5

Local File Systems



5.1    Introduction

VxWorks uses a standard I/O interface between the file system and the device driver. This allows multiple file systems, of the same or different types, to operate within a single VxWorks system. By following these standard interfaces, you can write your own file system for VxWorks, and freely mix file systems and device drivers.

This chapter discusses the VxWorks file systems, listed below, describing how they are organized, configured, and used.

  • dosFs.  

  • rawFS.  

  • tapeFs.  

  • cdromFs.  

  • TSFS (Target Server File System) .  

VxWorks also provides support for flash memory devices through the optional product TrueFFS. For more information, see 8. Flash Memory Block Device Driver.



5.2    MS-DOS-Compatible File System: dosFs

The dosFs file system is an MS-DOS-compatible file system that offers considerable flexibility appropriate to the multiple demands of real-time applications. The primary features are:

  • Hierarchical files and directories, allowing efficient organization and an arbitrary number of files to be created on a volume.

  • A choice of contiguous or non-contiguous files on a per-file basis.

  • Compatibility with widely available storage and retrieval media (diskettes, hard drives, and so on).

  • The ability to boot VxWorks from a dosFs file system.

  • Support for VFAT (Microsoft VFAT long file names) and VXLONGS (VxWorks long file names) directory formats.

  • Support for FAT12, FAT16, and FAT32 file allocation table types.

For API reference information about dosFs, see the entries for dosFsLib and dosFsFmtLib, as well as the cbioLib, dcacheCbio, and dpartCbio entries, in the VxWorks API Reference.

For information about the MS-DOS file system, please see the Microsoft documentation.


*      
NOTE: The discussion in this chapter of the dosFs file system uses the term sector to refer to the minimum addressable unit on a disk. This definition of the term follows most MS-DOS documentation. However, in VxWorks, these units on the disk are normally referred to as blocks, and a disk device is called a block device.

5.2.1   Creating a dosFs File System

This section summarizes the process of creating a dosFs file system, outlining the steps involved and diagramming where this process fits into the VxWorks system.

The process described in these steps corresponds to building the layers of VxWorks components between the hardware (such as a SCSI disk) and the I/O system, as illustrated in the sections within the dotted line in Figure 5-1.

Step 1:   Configure the Kernel

Configure your kernel with the dosFs, CBIO, and block device components. This step is described in 5.2.2 Configuring Your System.

Step 2:   Initialize the dosFs File System

This step is done automatically if you have included the required components in your project. This step is described in 5.2.3 Initializing the dosFs File System

Step 3:   Create the Block Device

Create either a block device or a CBIO driver device (ramDiskCbio). This step is described in 5.2.4 Creating a Block Device.

Step 4:   Create a Disk Cache

Creating a disk cache is optional. Disk cache is intended only for rotational media. This step is described in 5.2.5 Creating a Disk Cache.

Step 5:   Create the Partition for Use

Creating and mounting partitions is optional. This step is described in 5.2.6 Creating and Using Partitions.

Step 6:   Create a dosFs Device

Create the dosFs device. You can safely create the device whether or not you are using a pre-formatted disk. This step is described in 5.2.7 Creating a dosFs Device.

Step 7:   Format the Volume

If you are not using pre-formatted disks, format the volumes. This step is described in 5.2.8 Formatting the Volume.

Step 8:   Check the Disk Volume Integrity

Optionally, check the disk for volume integrity using dosFsChkDsk( ). Disk checking large disks can be time-consuming. The parameters you pass to dosFsDevCreate( ) determine whether disk checking happens automatically. For details, see the entry for dosFsDevCreate( ) in the VxWorks Reference Manual.

Step 9:   Mount the Disk

A disk volume is mounted automatically, generally during the first open( ) or creat( ) operation for a file or directory on the disk. This step is described in 5.2.9 Mounting Volumes.

Figure 5-1:   Configuring a VxWorks System with dosFs

5.2.2   Configuring Your System

To include dosFs in your VxWorks-based system, configure the kernel with the appropriate components for the dosFs file system.

Required Components

The following components are required:

  • INCLUDE_DOSFS_MAIN.  
dosFsLib (2)

  • INCLUDE_DOSFS_FAT.  
dosFs FAT12/16/32 FAT handler

  • INCLUDE_CBIO.  
CBIO API module

And, either one or both of the following components are required:

Microsoft VFAT direct handler

Strict 8.3 & VxLongNames directory handler

In addition, you need to include the appropriate component for your block device; for example, INCLUDE_SCSI or INCLUDE_ATA. Finally, add any related components that are required for your particular system.

Optional dosFs Components

Optional dosFs components are:

  • INCLUDE_DOSFS.  
usrDosFsOld.c wrapper layer

  • INCLUDE_DOSFS_FMT.  
dosFs2 file system formatting module

  • INCLUDE_DOSFS_CHKDSK.  
file system integrity checking

  • INCLUDE_DISK_UTIL.  
standard file system operations, such as ls, cd, mkdir, xcopy, and so on

  • INCLUDE_TAR.  
the tar utility

Optional CBIO Components

Optional CBIO components are:

  • INCLUDE_DISK_CACHE.  
CBIO API disk caching layer

  • INCLUDE_DISK_PART.  
disk partition handling code

  • INCLUDE_RAM_DISK.  
CBIO API RAM disk driver

5.2.3   Initializing the dosFs File System

Before any other operations can be performed, the dosFs file system library, dosFsLib, must be initialized. This happens automatically, triggered by the required dosFs components that were included in the system.

Initializing the file system invokes iosDrvInstall( ), which adds the driver to the I/O system driver table. The driver number assigned to the dosFs file system is recorded in a global variable, dosFsDrvNum. The table specifies the entry points for the dosFs file operations that are accessed by the devices using dosFs.

5.2.4   Creating a Block Device

Next, create one or more block devices. To create the device, call the routine appropriate for that device driver. The format for this routine is xxxDevCreate( ) where xxx represents the device driver type; for example, scsiBlkDevCreate( ) or ataDevCreate( ).

The driver routine returns a pointer to a block device descriptor structure, BLK_DEV. This structure describes the physical attributes of the device and specifies the routines that the device driver provides to a file system. For more information on block devices, see 4.9.4 Block Devices.

5.2.5   Creating a Disk Cache

If you have included the INCLUDE_DISK_CACHE component in your system, you can use dcacheDevCreate( ) to create a disk cache for each block device. Disk cache is intended to reduce the impact of seek times on rotational media, and is not used for RAM disks or TrueFFS devices. Example 5-1 creates a disk cache.

5.2.6   Creating and Using Partitions

If you have included the INCLUDE_DISK_PART component in your system, you can create partitions on a disk and mount volumes atop the partitions. Use the usrFdiskPartCreate( ) and dpartDevCreate( ) routines to do this.

The following two examples create and use partitions. The first example creates, partitions, and formats a disk. The second example uses the partitioned disk, from the first example, to create the partition handler.

Example 5-1:   Creating and Partitioning a Disk and Creating Volumes

This example takes a pointer to a block device, creates three partitions, creates the partition handler for these partitions, and creates the dosFs device handler for them. Then, it formats the partitions using dosFsVolFormat( ), which is discussed in the next section.

STATUS usrPartDiskFsInit 
    (  
    void * blkDevId               /* CBIO_DEV_ID or BLK_DEV*   */ 
    ) 
    { 
    const char * devNames[] = { "/sd0a", "/sd0b", "/sd0c" }; 
    int dcacheSize = 0x30000 ; 
    CBIO_DEV_ID cbio, cbio1 ; 
    /* create disk cache */ 
 
    if((cbio = dcacheDevCreate(blkDevId, NULL, dcacheSize, "/sd0")) 
        == NULL ) 
        return ERROR ; 
 
    /* create partitions */ 
 
    if((usrFdiskPartCreate (cbio,3,50,45)) == ERROR) 
        return ERROR; 
 
    /* create partition manager with FDISK style decoder, up to 3 parts */ 
 
    if((cbio1 = dpartDevCreate( cbio, 3, usrFdiskPartRead )) == NULL) 
        return ERROR; 
 
    /* create the 1st file system, 8 simult. open files, with CHKDSK */ 
 
    if(dosFsDevCreate( devNames[0], dpartPartGet(cbio1,0), 8, 0 ) == ERROR) 
        return ERROR; 
 
    /* create the 2nd file system, 6 simult. open files, with CHKDSK */ 
 
    if(dosFsDevCreate( devNames[1], dpartPartGet(cbio1,1), 6, 0 ) == ERROR) 
        return ERROR; 
 
    /* create the 3rd file system, 4 simultaneously open files, no CHKDSK */ 
 
    if(dosFsDevCreate( devNames[2], dpartPartGet(cbio1,2), 4, NONE ) 
       == ERROR) 
        return ERROR; 
 
    /* Formatting the first partition */ 
 
    if(dosFsVolFormat (devNames[0], 2,0) == ERROR) 
        return ERROR; 
 
    /* Formatting the second partition */ 
 
    if(dosFsVolFormat (devNames[1], 2,0) == ERROR) 
        return ERROR; 
 
    /* Formatting the third partition */ 
 
    if(dosFsVolFormat (devNames[2], 2,0) == ERROR) 
        return ERROR; 
 
    return OK; 
    } 

Example 5-2:   Accessing a Partitioned Disk

The following example configures a partitioned disk with three already existing partitions. Note that the ATA hard disk component allows for auto-mounting as many partitions as are referenced within its name parameter.

STATUS usrPartDiskFsInit 
    (  
    void * blkDevId              /* CBIO_DEV_ID or BLK_DEV*/ 
    ) 
    { 
    const char * devNames[] = { "/sd0a", "/sd0b", "/sd0c" }; 
    int dcacheSize = 0x30000 ; 
    CBIO_DEV_ID cbio, cbio1 ; 
 
    /* create disk cache */ 
 
    if((cbio = dcacheDevCreate(blkDevId, NULL, dcacheSize, "/sd0")) 
       == NULL ) 
        return ERROR ; 
 
    /* create partition manager with FDISK style decoder, up to 3 parts */ 
 
    if((cbio1 = dpartDevCreate( cbio, 3, usrFdiskPartRead )) == NULL) 
        return ERROR; 
 
    /* create the 1st file system, 8 simultaneously open files 
     * with CHKDSK  
     */ 
 
    if(dosFsDevCreate( devNames[0], dpartPartGet(cbio1,0), 8, 0 )  
       == ERROR) 
        return ERROR; 
 
    /* create the 2nd file sys, 6 simultaneously open files, with CHKDSK */ 
 
    if(dosFsDevCreate( devNames[1], dpartPartGet(cbio1,1), 6, 0 ) == ERROR) 
        return ERROR; 
 
    /* create the 3rd file system, 4 simultaneously open files, no CHKDSK */ 
 
    if(dosFsDevCreate( devNames[2], dpartPartGet(cbio1,2), 4, 0)  
       ==ERROR) 
        return ERROR; 
 
    return OK; 
    }

5.2.7   Creating a dosFs Device

Create a dosFs device using dosFsDevCreate( ), which calls iosDrvAdd( ) internally. This step simply adds the device to the I/O system; however, it does not invoke any I/O operations and, therefore, does not mount the disk.

This disk is not mounted until the first I/O operation occurs. For more information, see 5.4.4 Mounting Volumes.

5.2.8   Formatting the Volume

If you are using an unformatted disk, format the volume in either of two ways:

  • By calling dosFsVolFormat( ) directly, specifying options for both the format of the FAT and the directory format (described below).

  • By issuing the ioctl( ) FIODISKINIT command, which invokes the formatting routine with dosFsLib. This method uses the default volume format and parameters.

For more details, see the VxWorks API Reference entries for dosFsVolFormat( ) and ioctl( ).

The MS-DOS and dosFs file systems provide options for the format of the File Allocation Table (FAT) and the format of the directory. These options, described below, are completely independent.


*      
CAUTION: If you are using a disk that is already initialized with an MS-DOS boot sector, FAT, and root directory--for example, by using the FORMAT utility in MS-DOS--you can use dosFsDevCreate( ) to create a dosFs device. However, do not call dosFsVolFormat( ) or the file system data structures will be re-initialized (reformatted).

File Allocation Table (FAT) Formats

A volume FAT format is set during disk formatting, according to either the volume size (by default), or the per-user defined settings passed to dosFsVolFormat( ). FAT options are summarized in Table 5-1:

Table 5-1:   FAT Formats


Format
FAT Table Entry SIze
Usage
Size

FAT12
12 bits per cluster number
Appropriate for very small devices with up to 4,084 KB clusters.
Typically, each cluster is two sectors large.
FAT16
16 bits per cluster number
Appropriate for small disks of up to 65,524 KB clusters.
Typically, used for volumes up to 2 GB; can support up to 8 GB.
FAT32
32 bits (only 28 used) per cluster number
Appropriate for medium and larger disk drives.
By convention, used for volumes larger than 2 GB.

Directory Formats

There are three options for the directory format. These are:

  • MSFT Long Names (VFAT).

Uses case-insensitive long filenames, with up to 254 characters. This format accepts disks created with short names. MSFT Long Names1 is the default directory format.

  • Short Names (8.3).

Case-insensitive MS-DOS-style filenames (8.3), with eight uppercase characters for the name itself and three for the extension.

  • VxWorks Long Names (VxLong).

Wind River's proprietary VxWorks long name support, introduced prior to MSFT Long Names and used for backward compatibility with old dosFs VxLong disks. Allows case-sensitive filenames of up to 40 characters (consisting of any ASCII characters). The dot character (.), which indicates a file-name extension in MS-DOS, has no special significance in dosFs VxLong.


*      
NOTE: The VxWorks long names format supports 40-bit file size fields, allowing the file size to be larger than 4 GB.


*      
WARNING: If you use VxWorks Long Names, the disk will not be MS-DOS compatible. Use this long name support only for storing data local to VxWorks, on a disk that is initialized on a VxWorks system.

5.2.9   Mounting Volumes

A disk volume is mounted automatically, generally during the first open( ) or creat( ) operation for a file or directory on the disk. Certain ioctl( ) calls also cause the disk to be mounted.


*      
NOTE: The dosFs initialization procedure succeeds even if a volume is unformatted or a removable diskette is not inserted in the drive at the time the system boots. A system can boot successfully and initialize all its devices even if a drive has no removable media in it and the media's configuration and parameters are unknown.

5.2.10   Demonstrating with Examples

This section provides examples of the steps discussed in the sections above. These examples use a variety of configurations and device types. They are meant to be relatively generic and applicable to most block devices.

The first example uses an ATA disk, and includes detailed descriptions of the commands run from the shell, displaying both user input and command-line output. The second example lists the required steps to create and format a RAM disk volume. The last example demonstrates how to initialize a pre-formatted SCSI disk.


*      
CAUTION: Because device names are recognized by the I/O system using simple substring matching, file systems should not use a slash (/) alone as a name; unexpected results may occur.

Example 5-3:   Initializing an ATA Disk with dosFs2

This example demonstrates how to initialize an ATA disk with dosFs2. This example displays the commands and output from the VxWorks shell. While these steps use an ATA block device type, they are applicable to other block devices.

Step 1:   Create the Block Device

Create a block device (BLK_DEV) that controls the master ATA hard disk (drive zero) on the primary ATA controller (controller zero). This block device uses the entire disk.

-> pAta = ataDevCreate (0,0,0,0)  
new symbol "pAta" added to symbol table. 
pAta = 0x3fff334: value = 67105604 = 0x3fff344 = pAta + 0x10

Above, pAta is now a block device pointer (BLK_DEV *). The routine ataDevCreate( ) returns a valid value. A return value of NULL (0x0) indicates an error in ataDevCreate( ). Such an error usually indicates a BSP configuration or hardware configuration error.

This step is appropriate for any BLK_DEV device; for example, flash, SCSI, and so on. For related information, see the reference entry for ataDevCreate( ).

Step 2:   Create a Disk Cache

Next, create an (optional) disk data cache CBIO layer atop this disc:

-> pCbioCache = dcacheDevCreate (pAta,0,0,"ATA Hard Disk Cache")  
new symbol "pCbioCache" added to symbol table. 
pCbioCache = 0x3ffdbd0: value = 67105240 = 0x3fff1d8

Above, pCbioCache is a CBIO_DEV_ID, which is the handle for controlling the CBIO disk cache layer. For more information, see the reference entry for dcacheDevCreate( ).

Step 3:   Create Partitions

Then, create two partitions on this disk device, specifying 50% of the disk space for the second partition, leaving 50% for the first partition. This step should only be performed once, when the disk is first initialized. If partitions are already written to the disk, this step should not be performed since it destroys data:

-> usrFdiskPartCreate (pCbioCache, 2, 50, 0, 0)  
value = 0 = 0x0

For more information, see the entry for usrFdiskPartLibCbio( ) in the VxWorks Reference Manual.

In this step, the block device pointer pAta could have been passed instead of pCbioCache. Doing so would cause usrFdiskPartCreate( ) to use the BLK_DEV routines via a wrapper internally created by cbioWrapBlkDev( ).

Step 4:   Display Partitions

Now, you can optionally display the partitions created with usrFdiskPartShow( ):

-> usrFdiskPartShow (pAta) 
Master Boot Record - Partition Table 
-------------------------------------- 
Partition Entry number 00     Partition Entry offset 0x1be 
Status field = 0x80           Primary (bootable) Partition 
Type 0x06: MSDOS 16-bit FAT >=32M Partition 
Partition start LCHS: Cylinder 0000, Head 001, Sector 01 
Partition end   LCHS: Cylinder 0245, Head 017, Sector 39 
Sectors offset from MBR partition 0x00000027 
Number of sectors in partition 0x00262c17 
Sectors offset from start of disk 0x00000027
Master Boot Record - Partition Table 
-------------------------------------- 
Partition Entry number 01     Partition Entry offset 0x1ce 
Status field = 0x00           Non-bootable Partition 
Type 0x06: MSDOS 16-bit FAT >=32M Partition 
Partition start LCHS: Cylinder 0000, Head 018, Sector 01 
Partition end   LCHS: Cylinder 0233, Head 067, Sector 39 
Sectors offset from MBR partition 0x00262c3e 
Number of sectors in partition 0x00261d9e 
Sectors offset from start of disk 0x00262c3e
Master Boot Record - Partition Table 
-------------------------------------- 
Partition Entry number 02     Partition Entry offset 0x1de 
Status field = 0x00           Non-bootable Partition 
Type 0x00: Empty (NULL) Partition
Master Boot Record - Partition Table 
-------------------------------------- 
Partition Entry number 03     Partition Entry offset 0x1ee 
Status field = 0x00           Non-bootable Partition 
Type 0x00: Empty (NULL) Partition
value = 0 = 0x0 
->

Note above that two partitions have been created upon the disc, and the remaining two partition table entries are blank.


*      
NOTE: The CBIO device ID pCbioCache could have been passed to usrFdiskPartShow( ), instead of the block device pointer pAta. Doing so would cause usrFdiskPartShow( ) to use the CBIO disk cache routines to access the device.

Step 5:   Create a Partition Handler

Next, create a partition handler/mounter upon this disk. When using a disk cache layer, the partition handler code should always be instantiated after the disk cache layer:

-> pCbioParts = dpartDevCreate (pCbioCache,2, usrFdiskPartRead)  
new symbol "pCbioParts" added to symbol table. 
pCbioParts = 0x3ffd92c: value = 67099276 = 0x3ffda8c = pCbioParts + 0x160 
->

The call to dpartDevCreate( ) informs the partition layer to expect two partitions on the disk (24 is the maximum number of partitions the mounter can handle.) We have also instructed the partition manager (dpartCbio) to use the FDISK style partition mounter code usrFdiskPartRead( ).

For more information, see the reference entry for dpartDevCreate( ).

Step 6:   Create the dosFs2 File System

Create dosFs2 file systems atop each partition. The last argument specifies the integrated chkdsk configuration.

-> dosFsDevCreate ("/DOSA", dpartPartGet (pCbioParts,0), 16, 0)  
value = 0 = 0x0 
-> dosFsDevCreate ("/DOSB", dpartPartGet (pCbioParts,1), 16, -1) 
value = 0 = 0x0  
->
-> devs  
drv name 
0 /null 
1 /tyCo/0 
1 /tyCo/1 
6 ahostname: 
3 /DOSA       <---- First Partition  
3 /DOSB       <---- Second Partition 
value = 25 = 0x19 
->

This step defines the volume parameters and adds them to the IO system; it does not format the volumes. No disk I/O is performed during this step. The volumes are not mounted at this stage.

For more information, see the entry for dosFsLib in the VxWorks Reference Manual.

Step 7:   Format the DOS Volumes

Next, format the DOS volumes. This step need only be done once, when the volumes are first initialized. If the DOS volumes have already been initialized (formatted), then omit this step. The example formats the file system volumes with default options:

-> dosFsVolFormat ("/DOSA",0,0)  
Retrieved old volume params with %100 confidence: 
Volume Parameters: FAT type: FAT16, sectors per cluster 32 
2 FAT copies, 0 clusters, 153 sectors per FAT 
Sectors reserved 1, hidden 39, FAT sectors 306 
Root dir entries 512, sysId (null)  , serial number 8120000 
Label:"           " ... 
Disk with 2501655 sectors of 512 bytes will be formatted with: 
Volume Parameters: FAT type: FAT16, sectors per cluster 64 
2 FAT copies, 39082 clusters, 153 sectors per FAT 
Sectors reserved 1, hidden 39, FAT sectors 306 
Root dir entries 512, sysId VXDOS16 , serial number 8120000 
Label:"           " ... 
value = 0 = 0x0
-> dosFsVolFormat ("/DOSB",0,0)  
Retrieved old volume params with %100 confidence: 
Volume Parameters: FAT type: FAT16, sectors per cluster 32 
2 FAT copies, 0 clusters, 153 sectors per FAT 
Sectors reserved 1, hidden 39, FAT sectors 306 
Root dir entries 512, sysId (null) , serial number 9560000 
Label:"           " ...
Disk with 2497950 sectors of 512 bytes will be formatted with: 
Volume Parameters: FAT type: FAT16, sectors per cluster 64 
2 FAT copies, 39024 clusters, 153 sectors per FAT 
Sectors reserved 1, hidden 39, FAT sectors 306 
Root dir entries 512, sysId VXDOS16 , serial number 9560000 
Label:"           " ... 
value = 0 = 0x0 
->

For more information, see the entry for dosFsFmtLib in the VxWorks Reference Manual.

Step 8:   Access the Volumes

Now, the dosFs volumes are ready to access. Note that /DOSA will start a default chkdsk code and that /DOSB will not. Autochk is set via the fourth argument to dosFsDevCreate( ).

-> ll "/DOSA"  
/DOSA/  - disk check in progress ... 
/DOSA/  - Volume is OK
total # of clusters:        39,085 
# of free clusters:         39,083 
# of bad clusters:          0 
total free space:           1,221 Mb 
max contiguous free space:  1,280,671,744 bytes 
# of files:                 0 
# of folders:               0 
total bytes in files:       0 
# of lost chains:           0 
total bytes in lost chains: 0
Listing Directory /DOSA: 
value = 0 = 0x0
-> ll "/DOSB" 
Listing Directory /DOSB: 
value = 0 = 0x0 
->
-> dosFsShow "/DOSB" 
volume descriptor ptr (pVolDesc):       0x3f367c0 
cache block I/O descriptor ptr (cbio):  0x3f37be0 
auto disk check on mount:               NOT ENABLED 
max # of simultaneously open files:     18 
file descriptors in use:                0 
# of different files in use:            0 
# of descriptors for deleted files:     0 
# of  obsolete descriptors:             0
current volume configuration: 
- volume label:                NO LABEL ; (in boot sector: ) 
- volume Id:                   0x9560000 
- total number of sectors:     2,497,950 
- bytes per sector:            512 
- # of sectors per cluster:    64 
- # of reserved sectors:       1 
- FAT entry size:              FAT16 
- # of sectors per FAT copy:   153 
- # of FAT table copies:       2 
- # of hidden sectors:         39 
- first cluster is in sector # 339 
- Update last access date for open-read-close = FALSE 
- directory structure:         VFAT 
- root dir start sector:       307 
- # of sectors per root:       32 
- max # of entries in root:    512
FAT handler information: 
------------------------ 
- allocation group size:       4 clusters 
- free space on volume:        1,278,771,200 bytes 
value = 0 = 0x0 
->

Above, we can see the Volume parameters for the /DOSB volume. The file system volumes are now mounted and ready to be exercised.

If you are working with an ATA hard disk or a CD-ROM file system from an ATAPI CD-ROM drive, you can, alternatively, use usrAtaConfig( ). This routine processes several steps at once. For more information, see the reference entry.

Example 5-4:   Creating and Formatting a RAM Disk Volume

The following example creates a RAM disk of a certain size, and formats it for use with the dosFs file system. This example uses the ramDiskCbio module, which is intended for direct use with dosFsLib:

STATUS usrRamDiskInit 
    ( 
    void                              /* no argument */ 
    ) 
    { 
    int ramDiskSize = 128 * 1024 ;    /* 128KB, 128 bytes per sector */ 
    char *ramDiskDevName = "/ram0" ; 
    CBIO_DEV_ID cbio ; 
 
    /* 128 bytes/sec, 17 secs/track, auto-allocate */ 
 
    cbio = ramDiskDevCreate(NULL, 128, 17, ramDiskSize/128, 0) ; 
 
    if( cbio == NULL ) 
        return ERROR ; 
 
    /* create the file system, 4 simultaneously open files, no CHKDSK */ 
 
    dosFsDevCreate( ramDiskDevName, cbio, 4, NONE ); 
 
    /* format the RAM disk, ignore memory contents */ 
 
    dosFsVolFormat( cbio, DOS_OPT_BLANK | DOS_OPT_QUIET, NULL ); 
 
    return OK; 
    }

Example 5-5:   Initializing a SCSI Disk Drive

This example initializes a SCSI disk as a single file system volume (and assumes that the disk is already formatted).

STATUS usrScsiDiskInit 
    ( 
    int scsiId                        /* SCSI id */ 
    ) 
    {
    int dcacheSize = 128 * 1024 ;     /* 128KB disk cache */ 
    char *diskDevName = "/sd0" ;      /* disk device name */ 
    CBIO_DEV_ID cbio;                 /* pointer to a CBIO_DEV */ 
    BLK_DEV *pBlk;                    /* pointer to a BLK_DEV */ 
    SCSI_PHYS_DEV *pPhys ;            /* pointer to a SCSI physical device */ 
 
    /* Create the SCSI physical device */ 
 
    if ((pPhys = scsiPhysDevCreate  
        (pSysScsiCtrl, scsiId, 0, 0, NONE, 0,0, 0))==  NULL) 
        { 
        printErr ("usrScsiDiskInit: scsiPhysDevCreate SCSI ID %d failed.\n", 
                  scsiId, 0, 0, 0, 0, 0); 
        return ERROR; 
        } 
 
    /* Create the block device */ 
 
    if( (pblk = scsiBlkDevCreate(pPhys, 0, NONE )) == NULL ) 
        return ERROR; 
 
    /*  
     * works for ids less than 10 
     * append SCSI id to make the device name unique  
     */ 
 
    diskDevName[strlen(diskDevName)-1] += scsiId ; 
 
    /* create disk cache */ 
 
    if((cbio = dcacheDevCreate(pblk, NULL, dcacheSize, diskDevName))  
       == NULL ) 
        return ERROR ; 
 
    /* create the file system, 10 simultaneously open files, with CHKDSK */ 
 
    dosFsDevCreate( diskDevName, cbio, 10, 0 ); 
 
    return OK; 
    }

5.2.11   Working with Volumes and Disks

This section discusses issues related to disks and volumes.

For more information about ioctl( ) support functions, see 5.2.16 I/O Control Functions Supported by dosFsLib.

Announcing Disk Changes with Ready-Change

You can inform dosFsLib that a disk change is taking place by using the ready-change mechanism. A change in the disk's ready-status is interpreted by dosFsLib as indicating that the disk must be remounted before the next I/O operation. To announce a ready-change, use any of the following methods:

  • Call ioctl( ) with the FIODISKCHANGE function.

  • Have the device driver set the bd_readyChanged field in the BLK_DEV structure to TRUE; this has the same effect as notifying dosFsLib directly.

  • Use cbioRdyChgdSet( ) to set the ready-changed bit in the CBIO layer.

Accessing Volume Configuration Information

The dosFsShow( ) routine can be used to display volume configuration information. The dosFsVolDescGet( ) routine will programmatically obtain or verify a pointer to the DOS_VOLUME_DESC structure. For more information, see the reference entries.

Synchronizing Volumes

When a disk is synchronized, all modified buffered data is physically written to the disk, so that the disk is up to date. This includes data written to files, updated directory information, and the FAT. To avoid loss of data, a disk should be synchronized before it is removed. For more information, see the entries for close( ) and dosFsVolUnmount( ) in the VxWorks Reference Manual.

5.2.12   Working with Directories

This section discusses issues related to directories.

Creating Subdirectories

For FAT32, subdirectories can be created in any directory at any time. For FAT12 and FAT16, subdirectories can be created in any directory at any time, except in the root directory once it reaches its maximum entry count. Subdirectories can be created in the following ways:

  1. Using ioctl( ) with the FIOMKDIR function: The name of the directory to be created is passed as a parameter to ioctl( ).
  1. Using open( ): To create a directory, the O_CREAT option must be set in the flags parameter to open, and the FSTAT_DIR option must be set in the mode parameter. The open( ) call returns a file descriptor that describes the new directory. Use this file descriptor for reading only and close it when it is no longer needed.
  1. Use mkdir( ), usrFsLib.

When creating a directory using any of the above methods, the new directory name must be specified. This name can be either a full pathname or a pathname relative to the current working directory.

Removing Subdirectories

A directory that is to be deleted must be empty (except for the "." and ".." entries). The root directory can never be deleted. Subdirectories can be removed in the following ways:

  1. Using ioctl( ) with the FIORMDIR function, specifying the name of the directory. Again, the file descriptor used can refer to any file or directory on the volume, or to the entire volume itself.
  1. Using the remove( ) function, specifying the name of the directory.
  1. Use rmdir( ), usrFsLib.

Reading Directory Entries

You can programmatically search directories on dosFs volumes using the opendir( ), readdir( ), rewinddir( ), and closedir( ) routines.

To obtain more detailed information about a specific file, use the fstat( ) or stat( ) routine. Along with standard file information, the structure used by these routines also returns the file-attribute byte from a directory entry.

For more information, see the entry for dirLib in the VxWorks API Reference.

5.2.13   Working with Files

This section discusses issues related to files.

File I/O

Files on a dosFs file system device are created, deleted, written, and read using the standard VxWorks I/O routines: creat( ), remove( ), write( ), and read( ). For more information, see 4.3 Basic I/O, and the ioLib entries in the VxWorks API Reference.

File Attributes

The file-attribute byte in a dosFs directory entry consists of a set of flag bits, each indicating a particular file characteristic. The characteristics described by the file-attribute byte are shown in Table 5-2.

Table 5-2:   Flags in the File-Attribute Byte   


VxWorks Flag Name
Hex Value
Description

DOS_ATTR_RDONLY
0x01
read-only file
DOS_ATTR_HIDDEN
0x02
hidden file
DOS_ATTR_SYSTEM
0x04
system file
DOS_ATTR_VOL_LABEL
0x08
volume label
DOS_ATTR_DIRECTORY
0x10
subdirectory
DOS_ATTR_ARCHIVE
0x20
file is subject to archiving

DOS_ATTR_RDONLY

DOS_ATTR_HIDDEN

DOS_ATTR_SYSTEM

DOS_ATTR_VOL_LABEL

DOS_ATTR_DIRECTORY

DOS_ATTR_ARCHIVE

All the flags in the attribute byte, except the directory and volume label flags, can be set or cleared using the ioctl( ) function FIOATTRIBSET. This function is called after the opening of the specific file with the attributes to be changed. The attribute-byte value specified in the FIOATTRIBSET call is copied directly; to preserve existing flag settings, determine the current attributes using stat( ) or fstat( ), then change them using bitwise AND and OR operations.

Example 5-6:   Setting DosFs File Attributes

This example makes a dosFs file read-only, and leaves other attributes intact.

STATUS changeAttributes  
    ( 
    void 
    ) 
    { 
    int           fd; 
    struct stat   statStruct; 
 
    /* open file */ 
 
    if ((fd = open ("file", O_RDONLY, 0)) == ERROR) 
        return (ERROR); 
 
    /* get directory entry data */ 
 
    if (fstat (fd, &statStruct) == ERROR) 
        return (ERROR); 
 
    /* set read-only flag on file */ 
 
    if (ioctl (fd, FIOATTRIBSET, (statStruct.st_attrib | DOS_ATTR_RDONLY)) 
        == ERROR) 
        return (ERROR); 
 
    /* close file */ 
 
    close (fd); 
    return (OK); 
    } 


*      
NOTE: You can also use the attrib( ) routine to change file attributes. For more information, see the entry in usrFsLib.

5.2.14   Disk Space Allocation Options

The dosFs file system allocates disk space using one of the following methods. The first two methods are selected based upon the size of the write operation. The last method must be manually specified.

  • single cluster allocation.  

Single cluster allocation uses a single cluster, which is the minimum allocation unit. This method is automatically used when the write operation is smaller than the size of a single cluster.

  • cluster group allocation (nearly contiguous).  

Cluster group allocation uses adjacent (contiguous) groups of clusters, called extents. Cluster group allocation is nearly contiguous allocation and is the default method used when files are written in units larger than the size of a disk's cluster.

  • absolutely contiguous allocation .  

Absolutely contiguous allocation uses only absolutely contiguous clusters. Because this type of allocation is dependent upon the existence of such space, it is specified under only two conditions: immediately after a new file is created and when reading from a file assumed to have been allocated to a contiguous space. Using this method risks disk fragmentation.

For any allocation method, you can deallocate unused reserved bytes by using the POSIX-compatible routine ftruncate( ) or the ioctl( ) function FIOTRUNC.

Choosing an Allocation Method

Under most circumstances, cluster group allocation is preferred to absolutely contiguous file access. Because it is nearly contiguous file access, it achieves a nearly optimal access speed. Cluster group allocation also significantly minimizes the risk of fragmentation posed by absolutely contiguous allocation.

Absolutely contiguous allocation attains raw disk throughput levels, however this speed is only slightly faster than nearly contiguous file access. Moreover, fragmentation is likely to occur over time. This is because after a disk has been in use for some period of time, it becomes impossible to allocate contiguous space. Thus, there is no guarantee that new data, appended to a file created or opened with absolutely continuous allocation, will be contiguous to the initially written data segment.

It is recommended that for a performance-sensitive operation, the application regulate disk space utilization, limiting it to 90% of the total disk space. Fragmentation is unavoidable when filling in the last free space on a disk, which has a serious impact on performance.

Using Cluster Group Allocation

The dosFs file system defines the size of a cluster group based on the media's physical characteristics. That size is fixed for each particular media. Since seek operations are an overhead that reduces performance, it is desirable to arrange files so that sequential portions of a file are located in physically contiguous disk clusters. Cluster group allocation occurs when the cluster group size is considered sufficiently large so that the seek time is negligible compared to the read/write time. This technique is sometimes referred to as "nearly contiguous" file access because seek time between consecutive cluster groups is significantly reduced.

Because all large files on a volume are expected to have been written as a group of extents, removing them frees a number of extents to be used for new files subsequently created. Therefore, as long as free space is available for subsequent file storage, there are always extents available for use. Thus, cluster group allocation effectively prevents fragmentation (where a file is allocated in small units spread across distant locations on the disk). Access to fragmented files can be extremely slow, depending upon the degree of fragmentation.

Using Absolutely Contiguous Allocation

A contiguous file is made up of a series of consecutive disk sectors. Absolutely contiguous allocation is intended to allocate contiguous space to a specified file (or directory) and, by so doing, optimize access to that file. You can specify absolutely contiguous allocation either when creating a file, or when opening a file previously created in this manner.

For more information on the ioctl( ) functions, see 5.2.16 I/O Control Functions Supported by dosFsLib.

Allocating Contiguous Space for a File

To allocate a contiguous area to a newly created file, follow these steps:

  1. First, create the file in the normal fashion using open( ) or creat( ).
  1. Then, call ioctl( ). Use the file descriptor returned from open( ) or creat( ) as the file descriptor argument. Specify FIOCONTIG as the function code argument and the size of the requested contiguous area, in bytes, as the third argument.

The FAT is then searched for a suitable section of the disk. If found, this space is assigned to the new file. The file can then be closed, or it can be used for further I/O operations. The file descriptor used for calling ioctl( ) should be the only descriptor open to the file. Always perform the ioctl( ) FIOCONTIG operation before writing any data to the file.

To request the largest available contiguous space, use CONTIG_MAX for the size of the contiguous area. For example:

status = ioctl (fd, FIOCONTIG, CONTIG_MAX);
Allocating Space for Subdirectories

Subdirectories can also be allocated a contiguous disk area in the same manner:

  • If the directory is created using the ioctl( ) function FIOMKDIR, it must be subsequently opened to obtain a file descriptor to it.

  • If the directory is created using options to open( ), the returned file descriptor from that call can be used.

A directory must be empty (except for the "." and ".." entries) when it has contiguous space allocated to it.

Opening and Using a Contiguous File

Fragmented files require following cluster chains in the FAT. However, if a file is recognized as contiguous, the system can use an enhanced method that improves performance. This applies to all contiguous files, whether or not they were explicitly created using FIOCONTIG. Whenever a file is opened, it is checked for contiguity. If it is found to be contiguous, the file system registers the necessary information about that file to avoid the need for subsequent access to the FAT table. This enhances performance when working with the file by eliminating seek operations.

When you are opening a contiguous file, you can explicitly indicate that the file is contiguous by specifying the DOS_O_CONTIG_CHK flag with open( ). This prompts the file system to retrieve the section of contiguous space, allocated for this file, from the FAT table.

Demonstrating with an Example

To find the maximum contiguous area on a device, you can use the ioctl( ) function FIONCONTIG. This information can also be displayed by dosFsConfigShow( ).

Example 5-7:   Finding the Maximum Contiguous Area on a DosFs Device

In this example, the size (in bytes) of the largest contiguous area is copied to the integer pointed to by the third parameter to ioctl( ) (count).

STATUS contigTest  
    ( 
    void                    /* no argument */ 
    ) 
    { 
    int count;              /* size of maximum contiguous area in bytes */ 
    int fd;                 /* file descriptor */ 
 
    /* open device in raw mode */ 
 
    if ((fd = open ("/DEV1/", O_RDONLY, 0)) == ERROR) 
        return (ERROR); 
 
    /* find max contiguous area */ 
 
    ioctl (fd, FIONCONTIG, &count); 
 
    /* close device and display size of largest contiguous area */ 
 
    close (fd); 
    printf ("largest contiguous area = %d\n", count); 
    return (OK); 
    }

5.2.15   Crash Recovery and Volume Consistency

The DOS file system is inherently susceptible to data structure inconsistencies that result from interruptions during certain types of disk updates. These types of interruptions include power failures, inadvertent system crashes for fixed disks, and the manual removal of a disk.


*      
NOTE: The DOS file system is not considered a fault-tolerant file system.

The inconsistencies occur because the file system data for a single file is stored in three separate regions of the disk. The data stored in these regions are:

  • The file chain in the File Allocation Table (FAT), located in a region near the beginning of the disk.

  • The directory entry, located in a region anywhere on the disk.

  • File clusters containing file data, located anywhere on the disk.

Since all three regions cannot be always updated before an interruption, dosFs includes an optional integrated consistency-checking mechanism to detect and recover from inconsistencies. For example, if a disk is removed when a file is being deleted, a consistency check completes the file deletion operation. Or, if a file is being created when an interruption occurs, then the file is un-created. In other words, the consistency checker either rolls forward or rolls back the operation that has the inconsistency, making whichever correction is possible.

To use consistency checking, specify the autoChkLevel parameter to dosFsDevCreate( ), invoke it manually, or call the chkdsk( ) utility. If configured, consistency checking is invoked under the following conditions:

  • When a new volume is mounted.

  • Once at system initialization time for fixed disks.

  • Every time a new cartridge is inserted for removable disks.


*      
NOTE: Consistency checking slows a system down, particularly when a a disk is first accessed.

5.2.16   I/O Control Functions Supported by dosFsLib

The dosFs file system supports the ioctl( ) functions. These functions are defined in the header file ioLib.h along with their associated constants.

For more information, see the manual entries for dosFsLib and for ioctl( ) in ioLib.

Table 5-3:   I/O Control Functions Supported by dosFsLib   


Function
Decimal Value
Description

FIOATTRIBSET
35
Sets the file-attribute byte in the dosFs directory entry.
FIOCONTIG
36
Allocates contiguous disk space for a file or directory.
FIODISKCHANGE
13
Announces a media change.
FIODISKFORMAT
5
Formats the disk (device driver function).
FIODISKINIT
6
Initializes a dosFs file system on a disk volume.
FIOFLUSH
2
Flushes the file output buffer.
FIOFSTATGET
38
Gets file status information (directory entry data).
FIOGETNAME
18
Gets the filename of the fd.
FIOLABELGET
33
Gets the volume label.
FIOLABELSET
34
Sets the volume label.
FIOMKDIR
31
Creates a new directory.
FIOMOVE
47
Moves a file (does not rename the file).
FIONCONTIG
41
Gets the size of the maximum contiguous area on a device.
FIONFREE
30
Gets the number of free bytes on the volume.
FIONREAD
1
Gets the number of unread bytes in a file.
FIOREADDIR
37
Reads the next directory entry.
FIORENAME
10
Renames a file or directory.
FIORMDIR
32
Removes a directory.
FIOSEEK
7
Sets the current byte offset in a file.
FIOSYNC
21
Same as FIOFLUSH, but also re-reads buffered file data.
FIOTRUNC
42
Truncates a file to a specified length.
FIOUNMOUNT
39
Un-mounts a disk volume.
FIOWHERE
8
Returns the current byte position in a file.



5.3    Booting from a Local dosFs File System Using SCSI

VxWorks can be booted from a local SCSI device. Before you can boot from SCSI, you must make a new boot application ROM that contains the SCSI library. Include INCLUDE_SCSI, INCLUDE_SCSI_BOOT, and SYS_SCSI_CONFIG in your boot application project.

After burning the SCSI boot application ROM, you can prepare the dosFs file system for use as a boot device. The simplest way to do this is to partition the SCSI device so that a dosFs file system starts at block 0. You can then make the new system image, place it on your SCSI boot device, and boot the new VxWorks system. These steps are shown in more detail below.


*      
WARNING: For use as a boot device, the directory name for the dosFs file system must begin and end with slashes (as with /sd0/ used in the following example). This is an exception to the usual naming convention for dosFs file systems and is incompatible with the NFS requirement that device names not end in a slash.

Step 1:   Create the SCSI Device

Create the SCSI device using scsiPhysDevCreate( ) (see SCSI Drivers), and initialize the disk with a dosFs file system (see 5.2.3 Initializing the dosFs File System). Modify the file installDir/target/bspName/sysScsi.c to reflect your SCSI configuration.

Step 2:   Rebuild Your System

Rebuild your system.

Step 3:   Copy the VxWorks Runtime Image

Copy the file vxWorks to the drive. Below, a VxWorks task spawns the copy( ) routine, passing it two arguments.

The first argument is the source file for the copy( ) command. The source file is the VxWorks runtime image, vxWorks. The source host name is tiamat:, the source filename is C:/vxWorks. These are passed to copy( ) in concatenated form, as the string "tiamat:C:/vxWorks."

The second argument is the destination file for the copy( ) command. The dosFs file system, on the local target SCSI disk device, is named /sd0, and the target file name is vxWorks. These are, similarly, passed to copy( ) in concatenated form, as the string "/sd0/vxWorks." When booting the target from the SCSI device, the bootrom image should specify the runtime file as "/sd0/vxWorks".

-> sp (copy, "tiamat:c:/vxWorks","/sd0/vxWorks")  
task spawned: id = 0x3f2a200, name = t2 
value = 66232832 = 0x3f2a200
Copy OK: 1065570 bytes copied

Step 4:   Copy the System Symbol Table

Depending upon image configuration, the vxWorks.sym file for the system symbol table may also be needed. Therefore, in similar fashion, copy the vxWorks.sym file. The runtime image, vxWorks, downloads the vxWorks.sym file from the same location.

-> sp (copy, "tiamat:c:/vxWorks.sym","/sd0/vxWorks.sym")  
task spawned: id = 0x3f2a1bc, name = t3 
value = 66232764 = 0x3f2a1bc
Copy OK: 147698 bytes copied

Step 5:   Test the Copying

Now, list the files to ensure that the files were correctly copied.

-> sp (ll, "/sd0")  
task spawned: id = 0x3f2a1a8, name = t4 
value = 66232744 = 0x3f2a1a8 
->
Listing Directory /sd0: 
-rwxrwxrwx  1 0       0          1065570 Oct 26 2001 vxWorks 
-rwxrwxrwx  1 0       0           147698 Oct 26 2001 vxWorks.sym

Step 6:   Reboot and Change Parameters

Reboot the system, and then change the boot parameters. Boot device parameters for SCSI devices follow this format:

scsi=id,lun 

where id is the SCSI ID of the boot device, and lun is its Logical Unit Number (LUN). To enable use of the network, include the on-board Ethernet device (for example, ln for LANCE) in the other field.

The following example boots from a SCSI device with a SCSI ID of 2 and a LUN of 0.

    boot device            : scsi=2,0 
    processor number       : 0 
    host name              : host 
    file name              : /sd0/vxWorks 
    inet on ethernet (e)   : 147.11.1.222:ffffff00 
    host inet (h)          : 147.11.1.3 
    user (u)               : jane 
    flags (f)              : 0x0 
    target name (tn)       : t222 
    other                  : ln


5.4    Raw File System: rawFs

VxWorks provides a minimal "file system," rawFs, for use in systems that require only the most basic disk I/O functions. The rawFs file system, implemented in rawFsLib, treats the entire disk volume much like a single large file.

Although the dosFs file system provides this ability to varying degrees, the rawFs file system offers advantages in size and performance if more complex functions are not required.

To use the rawFs file system in a VxWorks-based system, include the INCLUDE_RAWFS component in the kernel, and set the NUM_RAWFS_FILES parameter to the desired maximum open file descriptor count.

5.4.1   Disk Organization

The rawFs file system imposes no organization of the data on the disk. It maintains no directory information; thus there is no division of the disk area into specific files. All open( ) operations on rawFs devices specify only the device name; no additional filenames are possible.

The entire disk area is treated as a single file and is available to any file descriptor that is open for the device. All read and write operations to the disk use a byte-offset relative to the start of the first block on the disk.

5.4.2   Initializing the rawFs File System

Before any other operations can be performed, the rawFs library, rawFsLib, must be initialized by calling rawFsInit( ). This routine takes a single parameter, the maximum number of rawFs file descriptors that can be open at one time. This count is used to allocate a set of descriptors; a descriptor is used each time a rawFs device is opened.

The rawFsInit( ) routine also makes an entry for the rawFs file system in the I/O system driver table (with iosDrvInstall( )). This entry specifies the entry points for rawFs file operations, for all devices that use the rawFs file system. The driver number assigned to the rawFs file system is placed in a global variable, rawFsDrvNum.

The rawFsInit( ) routine is normally called by the usrRoot( ) task after starting the VxWorks system.

5.4.3   Initializing a Device for Use With rawFs

After the rawFs file system is initialized, the next step is to create one or more devices. Devices are created by the device driver's device creation routine (xxDevCreate( )). The driver routine returns a pointer to a block device descriptor structure (BLK_DEV). The BLK_DEV structure describes the physical aspects of the device and specifies the routines in the device driver that a file system can call. For more information on block devices, see 4.9.4 Block Devices.

Immediately after its creation, the block device has neither a name nor a file system associated with it. To initialize a block device for use with rawFs, the already-created block device must be associated with rawFs and a name must be assigned to it. This is done with the rawFsDevInit( ) routine. Its parameters are the name to be used to identify the device and a pointer to the block device descriptor structure (BLK_DEV):

RAW_VOL_DESC *pVolDesc; 
BLK_DEV      *pBlkDev; 
pVolDesc = rawFsDevInit ("DEV1:", pBlkDev); 

The rawFsDevInit( ) call assigns the specified name to the device and enters the device in the I/O system device table (with iosDevAdd( )). It also allocates and initializes the file system's volume descriptor for the device. It returns a pointer to the volume descriptor to the caller; this pointer is used to identify the volume during certain file system calls.

Note that initializing the device for use with rawFs does not format the disk. That is done using an ioctl( ) call with the FIODISKFORMAT function.


*      
NOTE: No disk initialization (FIODISKINIT) is required, because there are no file system structures on the disk. Note, however, that rawFs accepts that ioctl( ) function code for compatibility with other file systems; in such cases, it performs no action and always returns OK.

5.4.4   Mounting Volumes

A disk volume is mounted automatically, generally during the first open( ) or creat( ) operation. (Certain ioctl( ) functions also cause the disk to be mounted.) The volume is again mounted automatically on the first disk access following a ready-change operation (see 5.4.6 Changing Disks).


*      
CAUTION: Because device names are recognized by the I/O system using simple substring matching, file systems should not use a slash (/) alone as a name; unexpected results may occur.

5.4.5   File I/O

To begin I/O operations upon a rawFs device, first open the device using the standard open( ) function. (The creat( ) function can be used instead, although nothing is actually "created.") Data on the rawFs device is written and read using the standard I/O routines write( ) and read( ). For more information, see 4.3 Basic I/O.

The character pointer associated with a file descriptor (that is, the byte offset where the read and write operations take place) can be set by using ioctl( ) with the FIOSEEK function.

Multiple file descriptors can be open simultaneously for a single device. These must be carefully managed to avoid modifying data that is also being used by another file descriptor. In most cases, such multiple open descriptors use FIOSEEK to set their character pointers to separate disk areas.

5.4.6   Changing Disks

The rawFs file system must be notified when removable disks are changed (for example, when floppies are swapped). Two different notification methods are provided: (1) rawFsVolUnmount( ) and (2) the ready-change mechanism.

Un-mounting Volumes

The first method of announcing a disk change is to call rawFsVolUnmount( ) prior to removing the disk. This call flushes all modified file descriptor buffers if possible (see Synchronizing Volumes) and also marks any open file descriptors as obsolete. The next I/O operation remounts the disk. Calling ioctl( ) with FIOUNMOUNT is equivalent to using rawFsVolUnmount( ). Any open file descriptor to the device can be used in the ioctl( ) call.

Attempts to use obsolete file descriptors for further I/O operations produce an S_rawFsLib_FD_OBSOLETE error. To free an obsolete descriptor, use close( ), as usual. This frees the descriptor even though it produces the same error.

ISRs must not call rawFsVolUnmount( ) directly, because the call can pend while the device becomes available. The ISR can instead give a semaphore that prompts a task to un-mount the volume. (Note that rawFsReadyChange( ) can be called directly from ISRs; see Announcing Disk Changes with Ready-Change.)

When rawFsVolUnmount( ) is called, it attempts to write buffered data out to the disk. Its use is therefore inappropriate for situations where the disk-change notification does not occur until a new disk is inserted, because the old buffered data would be written to the new disk. In this case, use rawFsReadyChange( ), as described in Announcing Disk Changes with Ready-Change.

If rawFsVolUnmount( ) is called after the disk is physically removed, the data flushing portion of its operation fails. However, the file descriptors are still marked as obsolete, and the disk is marked as requiring remounting. An error is not returned by rawFsVolUnmount( ); to avoid lost data in this situation, explicitly synchronize the disk before removing it (see Synchronizing Volumes).

Announcing Disk Changes with Ready-Change

The second method of announcing that a disk change is taking place is with the ready-change mechanism. A change in the disk's ready-status is interpreted by rawFsLib to indicate that the disk must be remounted during the next I/O call.

There are three ways to announce a ready-change:

  • By calling rawFsReadyChange( ) directly.

  • By calling ioctl( ) with FIODISKCHANGE.

  • By having the device driver set the bd_readyChanged field in the BLK_DEV structure to TRUE; this has the same effect as notifying rawFsLib directly.

The ready-change announcement does not cause buffered data to be flushed to the disk. It merely marks the volume as needing remounting. As a result, data written to files can be lost. This can be avoided by synchronizing the disk before asserting ready-change. The combination of synchronizing and asserting ready-change provides all the functionality of rawFsVolUnmount( ) except for marking file descriptors as obsolete.

Ready-change can be called from an ISR, because it does not attempt to flush data or perform other operations that could cause delay.

The block device driver status-check routine (identified by the bd_statusChk field in the BLK_DEV structure) is useful for asserting ready-change for devices that only detect a disk change after the new disk is inserted. This routine is called at the beginning of each open( ) or creat( ), before the file system checks for ready-change.

If it is not possible for a ready-change to be announced each time the disk is changed, close all file descriptors for the volume before changing the disk.

Synchronizing Volumes

When a disk is synchronized, all buffered data that is modified is written to the physical device so that the disk is up to date. For the rawFs file system, the only such data is that contained in open file descriptor buffers.

To avoid loss of data, synchronize a disk before removing it. You may need to explicitly synchronize a disk, depending on when (or if) the rawFsVolUnmount( ) call is issued.

When rawFsVolUnmount( ) is called, an attempt is made to synchronize the device before un-mounting. If this disk is still present and writable at the time of the call, synchronization takes place automatically; there is no need to synchronize the disk explicitly.

However, if the rawFsVolUnmount( ) call is made after a disk is removed, it is obviously too late to synchronize, and rawFsVolUnmount( ) discards the buffered data. Therefore, make a separate ioctl( ) call with the FIOSYNC function before removing the disk. (For example, this could be done in response to an operator command.) Any open file descriptor to the device can be used during the ioctl( ) call. This call writes all modified file descriptor buffers for the device out to the disk.

5.4.7   I/O Control Functions Supported by rawFsLib

The rawFs file system supports the ioctl( ) functions shown in Table 5-4. The functions listed are defined in the header file ioLib.h. For more information, see the manual entries for rawFsLib and for ioctl( ) in ioLib.

Table 5-4:   I/O Control Functions Supported by rawFsLib   


Function
Decimal Value
Description

FIODISKCHANGE
13
Announces a media change.
FIODISKFORMAT
5
Formats the disk (device driver function).
FIODISKINIT
6
Initializes the rawFs file system on a disk volume (optional).
FIOFLUSH
2
Same as FIOSYNC.
FIOGETNAME
18
Gets the device name of the fd.
FIONREAD
1
Gets the number of unread bytes on the device.
FIOSEEK
7
Sets the current byte offset on the device.
FIOSYNC
21
Writes out all modified file descriptor buffers.
FIOUNMOUNT
39
Un-mounts a disk volume.
FIOWHERE
8
Returns the current byte position on the device.



5.5    Tape File System: tapeFs

The tapeFs library, tapeFsLib, provides basic services for tape devices that do not use a standard file or directory structure on tape. The tape volume is treated much like a raw device where the entire volume is a large file. Any data organization on this large file is the responsibility of a higher-level layer.

To configure VxWorks with tapeFs, include the INCLUDE_TAPEFS component in the kernel.


*      
NOTE: The tape file system must be configured with SCSI-2 enabled. See SCSI Drivers for configuration details.

5.5.1   Tape Organization

The tapeFs file system imposes no organization of the data on the tape volume. It maintains no directory information; there is no division of the tape area into specific files; and no filenames are used. An open( ) operation on the tapeFs device specifies only the device name; no additional filenames are allowed.

The entire tape area is available to any file descriptor open for the device. All read and write operations to the tape use a location offset relative to the current location of the tape head. When a file is configured as a rewind device and first opened, tape operations begin at the beginning-of-medium (BOM); see Initializing a Device for Use With tapeFs. Thereafter, all operations occur relative to where the tape head is located at that instant of time. No location information, as such, is maintained by tapeFs.

5.5.2   Initializing the tapeFs File System

The tapeFs file system must be initialized, and a tape device created, in order to make the physical tape device available to the tape file system. At this point, normal I/O system operations can be performed.

The tapeFs library, tapeFsLib, is initialized by calling tapeFsInit( ). Each tape file system can handle multiple tape devices. However, each tape device is allowed only one file descriptor. Thus, you cannot open two files on the same tape device.

The tapeFsInit( ) routine also makes an entry for the tapeFs file system in the I/O system driver table (with iosDrvInstall( )). This entry specifies function pointers to carry out tapeFs file operations on devices that use the tapeFs file system. The driver number assigned to the tapeFs file system is placed in a global variable, tapeFsDrvNum.

When initializing a tape device, tapeFsInit( ) is called automatically if tapeFsDevInit( ) is called; thus, the tape file system does not require explicit initialization.

Initializing a Device for Use With tapeFs

Once the tapeFs file system has been initialized, the next step is to create one or more devices that can be used with it. This is done using the sequential device creation routine, scsiSeqDevCreate( ). The driver routine returns a pointer to a sequential device descriptor structure, SEQ_DEV. The SEQ_DEV structure describes the physical aspects of the device and specifies the routines in the device driver that tapeFs can call. For more information on sequential devices, see the manual entry for scsiSeqDevCreate( ), Configuring SCSI Drivers and 4.9.4 Block Devices.

Immediately after its creation, the sequential device has neither a name nor a file system associated with it. To initialize a sequential device for use with tapeFs, call tapeFsDevInit( ) to assign a name and declare a file system. Its parameters are the volume name--for identifying the device; a pointer to SEQ_DEV--the sequential device descriptor structure; and a pointer to an initialized tape configuration structure TAPE_CONFIG. This structure has the following form:

typedef struct  /* TAPE_CONFIG tape device config structure */ 
    { 
    int blkSize;           /* block size; 0 => var. block size */ 
    BOOL rewind;           /* TRUE => a rewind device; FALSE => no rewind */ 
    int numFileMarks;      /* not used */ 
    int density;           /* not used */ 
    } TAPE_CONFIG;

In the preceding definition of TAPE_CONFIG, only two fields, blkSize and rewind, are currently in use. If rewind is TRUE, then a tape device is rewound to the beginning-of-medium (BOM) upon closing a file with close( ). However, if rewind is FALSE, then closing a file has no effect on the position of the read/write head on the tape medium.

The blkSize field specifies the block size of the physical tape device. Having set the block size, each read or write operation has a transfer unit of blkSize. Tape devices can perform fixed or variable block transfers, a distinction also captured in the blkSize field.

For more information on initializing a tapeFs device, see the VxWorks API Reference entry for tapeFsDevInit( ).

Systems with Fixed Block and Variable Block Devices

A tape file system can be created for fixed block size transfers or variable block size transfers, depending on the capabilities of the underlying physical device. The type of data transfer (fixed block or variable block) is usually decided when the tape device is being created in the file system, that is, before the call to tapeFsDevInit( ). A block size of zero represents variable block size data transfers.

Once the block size has been set for a particular tape device, it is usually not modified. To modify the block size, use the ioctl( ) functions FIOBLKSIZESET and FIOBLKSIZEGET to set and get the block size on the physical device.

Note that for fixed block transfers, the tape file system buffers a block of data. If the block size of the physical device is changed after a file is opened, the file should first be closed and then re-opened in order for the new block size to take effect.

Example 5-8:   Tape Device Configuration

There are many ways to configure a tape device. In this code example, a tape device is configured with a block size of 512 bytes and the option to rewind the device at the end of operations.

/* global variables assigned elsewhere */ 
 
SCSI_PHYS_DEV *   pScsiPhysDev; 
 
/* local variable declarations */ 
 
TAPE_VOL_DESC *  pTapeVol; 
SEQ_DEV  *       pSeqDev; 
TAPE_CONFIG      tapeConfig; 
 
/* initialization code */ 
 
tapeConfig.blkSize  = 512; 
tapeConfig.rewind   = TRUE; 
pSeqDev    = scsiSeqDevCreate (pScsiPhysDev); 
pTapeVol   = tapeFsDevInit ("/tape1", pSeqDev, tapeConfig); 

The tapeFsDevInit( ) call assigns the specified name to the device and enters the device in the I/O system device table (with iosDevAdd( )). The return value of this routine is a pointer to a volume descriptor structure that contains volume-specific configuration and state information.

5.5.3   Mounting Volumes

A tape volume is mounted automatically during the open( ) operation. There is no specific mount operation, that is, the mount is implicit in the open( ) operation.


*      
CAUTION: Because device names are recognized by the I/O system using simple substring matching, file systems should not use a slash (/) alone as a name; unexpected results may occur.

The tapeFs tape volumes can be operated in only one of two modes: read-only (O_RDONLY) or write-only (O_WRONLY). There is no read-write mode. The mode of operation is defined when the file is opened using open( ).

5.5.4   File I/O

To begin I/O to a tapeFs device, the device is first opened using open( ). Data on the tapeFs device is written and read using the standard I/O routines write( ) and read( ). For more information, see 4.7.7 Block Devices.

End-of-file markers can be written using ioctl( ) with the MTWEOF function. For more information, see 5.5.6 I/O Control Functions Supported by tapeFsLib.

5.5.5   Changing Tapes

The tapeFs file system should be notified when removable media are changed (for example, when tapes are swapped). The tapeFsVolUnmount( ) routine controls the mechanism to un-mount a tape volume.

A tape should be un-mounted before it is removed. Prior to un-mounting a tape volume, an open file descriptor must be closed. Closing an open file flushes any buffered data to the tape, thus synchronizing the file system with the data on the tape. To flush or synchronize data before closing the file, call ioctl( ) with the FIOFLUSH or FIOSYNC functions.

After closing any open file, call tapeFsVolUnmount( ) before removing the tape. Once a tape has been un-mounted, the next I/O operation must remount the tape using open( ).

Interrupt handlers must not call tapeFsVolUnmount( ) directly, because it is possible for the call to pend while the device becomes available. The interrupt handler can instead give a semaphore that prompts a task to un-mount the volume.

5.5.6   I/O Control Functions Supported by tapeFsLib

The tapeFs file system supports the ioctl( ) functions shown in Table 5-5. The functions listed are defined in the header files ioLib.h, seqIo.h, and tapeFsLib.h. For more information, see the VxWorks API Reference entries for tapeFsLib, ioLib, and ioctl( ).

Table 5-5:   I/O Control Functions Supported by tapeFsLib   


Function
Decimal Value
Description

FIOFLUSH
2
Writes out all modified file descriptor buffers.
FIOSYNC
21
Same as FIOFLUSH.
FIOBLKSIZEGET
1001
Gets the actual block size of the tape device by issuing a driver command to it. Check this value with that set in the SEQ_DEV data structure.
FIOBLKSIZESET
1000
Sets the block size of the tape device on the device and in the SEQ_DEV data structure.
MTIOCTOP
1005
Performs a UNIX-like MTIO operation to the tape device. The type of operation and operation count is set in an MTIO structure passed to the ioctl( ) routine. The MTIO operations are defined in Table 5-6.

The MTIOCTOP operation is compatible with the UNIX MTIOCTOP operation. The argument passed to ioctl( ) with MTIOCTOP is a pointer to an MTOP structure that contains the following two fields:

typedef struct mtop  
    { 
    short mt_op;       /* operation */ 
    int  mt_count;     /* number of operations */ 
    } MTOP;

The mt_op field contains the type of MTIOCTOP operation to perform. These operations are defined in Table 5-6. The mt_count field contains the number of times the operation defined in mt_op should be performed.  

Table 5-6:   MTIOCTOP Operations   


Function
Value
Meaning

MTWEOF
0
Writes an end-of-file record or "file mark."
MTFSF
1
Forward-spaces over a file mark.
MTBSF
2
Backward-spaces over a file mark.
MTFSR
3
Forward-spaces over a data block.
MTBSR
4
Backward space over a data block.
MTREW
5
Rewinds the tape device to the beginning-of-medium.
MTOFFL
6
Rewinds and puts the drive offline.
MTNOP
7
No operation; sets status in the SEQ_DEV structure only.
MTRETEN
8
Re-tensions the tape (cartridge tape only).
MTERASE
9
Erases the entire tape.
MTEOM
10
Positions the tape to end-of-media.
MTNBSF
11
Backward-spaces file to the beginning-of-medium.

  



5.6    CD-ROM File System: cdromFs

The cdromFs library, cdromFsLib, lets applications read any CD-ROM that is formatted in accordance with ISO 9660 file system standards. To configure VxWorks with cdromFs, add the INCLUDE_CDROMFS component to the kernel.

After initializing cdromFs and mounting it on a CD-ROM block device, you can access data on that device using the standard POSIX I/O calls: open( ), close( ), read( ), ioctl( ), readdir( ), and stat( ). The write( ) call always returns an error.

The cdromFs utility supports multiple drives, multiple open files, and concurrent file access. When you specify a pathname, cdromFS accepts both "/" and "\". However, the backslash is not recommended because it might not be supported in future releases.

CdromFs provides access to CD-ROM file systems using any standard BLK_DEV structure. The basic initialization sequence is similar to installing a dosFs file system on a SCSI device.

For information on using cdromFs( ), see the VxWorks API Reference entry for cdromFsLib.

Example 5-9:   Creating and Using a CD-ROM Block Device

The example below describes the steps for creating a block device for the CD-ROM, creating a cdromFsLib device, mounting the filesystem, and accessing the media in the device.

Step 1:   Configure Your Environment for the CD-ROM Device

Add the component INCLUDE_CDROMFS in your project. Add other required components (like SCSI/ATA depending on the type of device). For more information, see 5.2.2 Configuring Your System.

If you are using an ATAPI device, make appropriate modifications to the ataDrv, ataResources[ ] structure array (if needed). This must be configured appropriately for your hardware platform.

Step 2:   Create a Block Device

Based on the type of device, use the appropriate create routine and create a block device. Following is an example for an ATAPI master device upon the secondary ATA controller:

-> pBlkd = ataDevCreate(1, 0, 0, 0)  
new symbol "pBlkd" added to symbol table. 
pBlkd = 0x3fff334: value = 67105604 = 0x3fff344 = pBlkd + 0x10

Step 3:   Create an Instance of a CD-ROM Device in the I/O System

A block device must already have been created. Call cdromFsDevCreate( ), which calls iosDrvInstall( ) internally. This enters the appropriate driver routines in the I/O driver table.

-> cdromFsDevCreate("/cd", pBlkd)  
value = 67105456 = 0x3fff2b0
-> devs  
drv name 
0 /null 
1 /tyCo/0 
1 /tyCo/1 
5 ala-petrient: 
6 /vio 
7 /cd 
value = 25 = 0x19
-> cd "/cd"  
value = 0 = 0x0

The cd command changes the current working directory, without performing I/O, and, therefore, can be called before the media is mounted.

Step 4:   Mount the Device

Now, run cdromFsVolConfigShow( ) to indicate whether the device (created above) is mounted. Executing this command shows that the device is not mounted.

-> cdromFsVolConfigShow "/cd" 
device config structure ptr     0x3fff2b0 
device name                     /cd 
bytes per blkDevDrv sector      2048 
no volume mounted 
value = 18 = 0x12

Mount the device in order to access it. Because cdromFs is mounted during the first open( ) operation, a call to open( ) or any function that uses open( ) will mount the device. The ls command below both mounts the device and lists its contents.

-> ls "/cd"  
/cd/. 
/cd/.. 
/cd/INDEX.HTML;1 
/cd/INSTRUCT.HTML;1 
/cd/MPF 
/cd/README.TXT;1 
value = 0 = 0x0

Step 5:   Check the Configuration

You can check the CD-ROM configuration using cdromFsVolConfigShow( ):

-> cdromFsVolConfigShow "/cd" 
device config structure ptr     0x3fff2b0 
device name                     /cd 
bytes per blkDevDrv sector      2048
Primary directory hierarchy:
standard ID                     :CD001 
volume descriptor version       :1 
system ID                       :LINUX 
volume ID                       :MPF_CD 
volume size                     :611622912 = 583 MB 
number of logical blocks        :298644 = 0x48e94 
volume set size                 :1 
volume sequence number          :1 
logical block size              :2048 
path table size (bytes)         :3476 
path table entries              :238 
volume set ID                   :
volume publisher ID             :WorldWide Technolgies
volume data preparer ID         :Kelly Corday
volume application ID           :mkisofs v1.04
copyright file name             :mkisofs v1.04 
abstract file name              :mkisofs v1.04 
bibliographic file name         :mkisofs v1.04 
creation date                   :13.11.1998  14:36:49:00 
modification date               :13.11.1998  14:36:49:00 
expiration date                 :00.00.0000  00:00:00:00 
effective date                  :13.11.1998  14:36:49:00 
value = 0 = 0x0


5.7    The Target Server File System: TSFS

The Target Server File System (TSFS) is designed for development and diagnostic purposes. It is a full-featured VxWorks file system, but the files are actually located on the host system.

The TSFS provides all of the I/O features of the network driver for remote file access (netDrv), without requiring any target resources (except those required for communication between the target system and the target server on the host). The TSFS uses a WDB driver to transfer requests from the VxWorks I/O system to the target server. The target server reads the request and executes it using the host file system. When you open a file with TSFS, the file being opened is actually on the host. Subsequent read( ) and write( ) calls on the file descriptor obtained from the open( ) call read from and write to the opened host file.

The TSFS VIO driver is oriented toward file I/O rather than toward console operations as is the Tornado 1.0 VIO driver. TSFS provides all the I/O features that netDrv provides, without requiring any target resource beyond what is already configured to support communication between target and target server. It is possible to access host files randomly without copying the entire file to the target, to load an object module from a virtual file source, and to supply the filename to routines such as moduleLoad( ) and copy( ).

Each I/O request, including open( ), is synchronous; the calling target task is blocked until the operation is complete. This provides flow control not available in the console VIO implementation. In addition, there is no need for WTX protocol requests to be issued to associate the VIO channel with a particular host file; the information is contained in the name of the file.

Consider a read( ) call. The driver transmits the ID of the file (previously established by an open( ) call), the address of the buffer to receive the file data, and the desired length of the read to the target server. The target server responds by issuing the equivalent read( ) call on the host and transfers the data read to the target program. The return value of read( ) and any errno that might arise are also relayed to the target, so that the file appears to be local in every way.

For detailed information, see the VxWorks API Reference entry for wdbTsfsDrv.

Socket Support

TSFS sockets are operated on in a similar way to other TSFS files, using open( ), close( ), read( ), write( ), and ioctl( ). To open a TSFS socket, use one of the following forms of filename:

"TCP:hostIP:port" 
"TCP:hostname:port"

The flags and permissions arguments are ignored. The following examples show how to use these filenames:

fd = open("/tgtsvr/TCP:phobos:6164"0,0)    /* open socket and connect  */ 
                                           /* to server phobos         */ 
 
fd = open("/tgtsvr/TCP:150.50.50.50:6164",0,0)  /* open socket and     */ 
                                                /*connect to server    */ 
                                                /* 150.50.50.50        */

The result of this open( ) call is to open a TCP socket on the host and connect it to the target server socket at hostname or hostIP awaiting connections on port. The resultant socket is non-blocking. Use read( ) and write( ) to read and write to the TSFS socket. Because the socket is non-blocking, the read( ) call returns immediately with an error and the appropriate errno if there is no data available to read from the socket. The ioctl( ) usage specific to TSFS sockets is discussed in the VxWorks API Reference entry for wdbTsfsDrv. This socket configuration allows VxWorks to use the socket facility without requiring sockLib and the networking modules on the target.

Error Handling

Errors can arise at various points within TSFS and are reported back to the original caller on the target, along with an appropriate error code. The error code returned is the VxWorks errno which most closely matches the error experienced on the host. If a WDB error is encountered, a WDB error message is returned rather than a VxWorks errno.

TSFS Configuration

To use the TSFS, your VxWorks-based system must be configured with the INCLUDE_WDB_TSFS component in the kernel. This creates the /tgtsvr file system.

The target server on the host system must also be configured for TSFS. This involves assigning a root directory on your host to TSFS (see the discussion of the target server -R option in Security Considerations). For example, on a PC host you could set the TSFS root to c:\myTarget\logs.

Having done so, opening the file /tgtsvr/logFoo on the target causes c:\myTarget\logs\logFoo to be opened on the host by the target server. A new file descriptor representing that file is returned to the caller on the target.

Security Considerations

While TSFS has much in common with netDrv, the security considerations are different. With TSFS, the host file operations are done on behalf of the user that launched the target server. The user name given to the target as a boot parameter has no effect. In fact, none of the boot parameters have any effect on the access privileges of TSFS.

In this environment, it is less clear to the user what the privilege restrictions to TSFS actually are, since the user ID and host machine that start the target server may vary from invocation to invocation. By default, any Tornado tool that connects to a target server which is supporting TSFS has access to any file with the same authorizations as the user that started that target server. However, the target server can be locked (with the -L option) to restrict access to the TSFS.

The options which have been added to the target server startup routine to control target access to host files using TSFS include:

-L Lock the target server.     

-R Set the root of TSFS.

-RW Make TSFS read-write.


*      
NOTE: For more information about the target server and the TSFS, see the tgtsvr entry in the online Tornado Tools Reference. For information about specifying target server options from the Tornado IDE, see the Tornado User's Reference: Target Manager.

  

1:  The MSFT Long Names (VFAT) format supports 32-bit file size fields, limiting the file size to a 4 GB maximum.