Directory Searching

Visual Prolog includes directory search predicates, enabling file name matching with wildcards. In addition, the predicates return all relevant information about the directory entries found.

Directory searching is very file system dependent and you should therefore guard yourself against future changes by isolating these predicates when they're used. Don't spread them all over your application, and don't rely on their arguments and functionality remaining unchanged. In fact, don't even rely on them being the same for different versions of OS/2: the installable file system concept means that future versions of OS/2 may behave very differently, although great effort will be spent in attempts to provide portability. Below, all references to OS/2 refer to the FAT file system.

Basically, to find matching files the directory has to be opened; this is done with the diropen predicate, specifying the file name mask and the attributes to look for. Then, by calling the dirmatch predicate, the matching files are found one by one. Finally, the directory is closed by a call to the dirclose predicate.

Generally, the predicates behave identically irrespective of platform: a file name - optionally containing wildcards - is used to specify the names to match, and a set of search attributes refine the match (for a list of attributes, see the section on File Attributes earlier in this chapter). However, unlike the DOS and OS/2 directory search mechanisms, the search attributes don't increase the search beyond 'normal' files. Visual Prolog considers all attributes as strictly informational, and they may all be used for file selection. When using the directory search predicates, you should therefore specify the attributes you are interested in: if you for instance want everything with the archive bit set, specify fa_arch; if you want everything with the system bit set, specify fa_system; if you want 'normal' files, specify fa_normal, etc. You should be aware, though, that the attributes specified are inclusive of each other: if several attributes are combined, the directory search will find everything matching at least one of the attributes, but the entry found won't necessarily match all the attributes. In other words, using set terminology, it is the union of the matching files, not the intersection, which is returned. Exactly what is found may be determined by bitwise testing of the returned attribute.

UNIX users should be aware that only one kind of directory entry (such as a normal file, a pipe, a directory, etc.) may be searched for at a time. No permissions of the entries are considered, and none should be specified.

(1) diropen/3

diropen is used to gain access to a directory. It takes the following format:

    diropen(Wild,Attrib,Block)                          /* (i,i,o) */

where Wild is a file name, optionally containing wildcards, Attrib are the required search attributes, and Block is an information block used by subsequent calls to dirmatch. To the compiler this block looks like a string, but it contains more information than meets the eye. Therefore, it cannot be asserted in a database and then retracted for use at a later stage - as long as the directory is open, it must be held in a local variable (or an argument, which is the same thing). diropen will fail if there are no files matching the specification; however, if the file name includes a directory which doesn't exist, diropen will exit with an error.

Several diropens may be active simultaneously; in other words, they may be nested and used recursively.

(2) dirmatch/10

dirmatch will, after diropen has returned an information block, return the name and other information for each matching file, one at each call. It looks as follows:

    dirmatch(Block,Name,Attr,Hour,Min,TwoSec,Year,Month,Day,Size)
                                                                          /* (i,o,o,o,o,o,o,o,o,o) */

The Block is the information block returned by diropen, Name is the matching name, and Attr are the attributes for the entry found. The rest of the arguments should be self-explanatory - they're all unsigned integers, apart from Size, which is an unsigned long. Note that DOS and OS/2 use only 5 bits to encode the seconds part of the time stamp, giving at most 32 different values - hence the TwoSec name.

Upon each call, dirmatch returns the next matching directory entry. When there are no more matches, dirmatch fails; if this happens, dirmatch will automatically close the directory.

You should be aware that if searching for subdirectories with a name specification of e.g. "*.*", dirmatch will always return the entries "." and ".." if these are returned by the operating system. Therefore, dirmatch is likely to find directories in all directories except perhaps the root.

(3) dirclose/1

dirclose will close a previously opened directory. It takes the form:

    dirclose(Block)                                         /* (i) */

where Block is the information block returned by diropen. Note that if dirmatch is repeatedly called until it fails (because there are no more matching files), dirclose should not be called, as dirmatch will have closed the directory.

Example

The following demonstrates the use of the directory matching predicates, to make an existdir predicate to complement the existfile standard predicate described previously.

/* Program ch12e16.pro */

PREDICATES

    existdir(string)

    exd1(string)

    exd2(string,string)

 

CLAUSES

    existdir(Wild):-

        diropen(Wild,fa_subdir,Block),

        exd1(Block),

        dirclose(Block).

 

    exd1(Block):-

        dirmatch(Block,Name,_,_,_,_,_,_,_,_),

        exd2(Block,Name).

 

    exd2(_,Name):-

        not(frontchar(Name,'.',_)),!.

    exd2(Block,_):-

        exd1(Block).

Given for instance the goal existdir("c:¡¬¡¬*.*") in DOS, it will - unless you have a rather extraordinary disk organization - say 'yes'. However, it will only find subdirectories in existing paths - if you ask for e.g. existdir(c:¡¬¡¬jnk¡¬¡¬*.*") without having a directory called 'JNK' in the root of drive c, it will exit with an error. You should also be aware that in DOS the root itself can't be matched: there is no directory called '¡¬', and existdir("c:¡¬¡¬") will fail. This is an operating system defined restriction of DOS, and is not relevant in UNIX where '/' does exist.

Note, by the way, how the current and parent directory entries ("." and "..") are filtered out in the example.

(4) dirfiles/11

Having presented the hard way of finding files, here's the easy way. dirfiles is a non- deterministic standard predicate which, upon backtracking, returns all matching files one by one. It looks as follows:

    dirfiles(Wild,Attrib,Fnam,RetAttr,Hour,Min,Sec,
    Year,Month,Day,Size)
                /* (i,i,o,o,o,o,o,o,o,o,o) */

The use of dirfiles obviates the need to open and close the directory as this is handled automatically, but there is a condition attached: in order to use it correctly, it must be backtracked into until it fails. It is the final failure of the predicate which automatically closes the directory. You should be aware that neither the compiler nor the code supporting a running program has any way of detecting if this won't happen - it is entirely the programmers responsibility. Having said that, no serious harm will come from leaving a couple of directories open, but eventually the system will run out of handles.

As with diropen, calls to dirfiles may be nested and used recursively.

DOS Example

Below is a sample program which will traverse all directories on drive C, searching for entries having the 'system' or 'hidden' attribute set. The OS will typically have a couple of hidden files in the root directory. However, if there are hidden files elsewhere on the disk, be suspicious! They're probably harmless copy- protection or configuration files for commercial software you have installed, but why hide any files?

/* Program ch12e17.pro */

CONSTANTS

    fa_hidden  = $02    /* Hidden file          */

    fa_system = $04    /* System file          */

    fa_subdir   = $10    /* Subdirectory         */

    fa_hidsys  = $06    /* hidden + system */

 

PREDICATES

    findhidden(string,string)

    wrattr(integer)

 

CLAUSES

    wrattr(A):-

        bitand(A,fa_hidden,AA),

        AA<>0,write('H'),fail.

    wrattr(A):-bitand(A,fa_system,AA),

        AA<>0,write('S'),fail.

    wrattr(A):-

        bitand(A,fa_subdir,AA),

        AA<>0,write('D'),fail.

    wrattr(_).

    findhidden(CurrPath,Wild):-

        write(CurrPath,":¡¬n"),

        filenamepath(FileSpec,CurrPath,Wild),

        dirfiles(FileSpec,fa_hidsys,FileName,RetAttr,_,_,_,_,_,_,_),

        wrattr(RetAttr),

        write('¡¬t',FileName,'¡¬n'),

        fail.

    findhidden(CurrPath,Wild):-

        filenamepath(DirSpec,CurrPath,"*.*"),

        dirfiles(DirSpec,fa_subdir,Name,_,_,_,_,_,_,_,_),

        not(frontchar(Name,'.',_)),

        filenamepath(DirName,CurrPath,Name),

        findhidden(DirName,Wild),

        fail.

    findhidden(_,_).

GOAL

    findhidden("C:¡¬¡¬","*.*").

This example also demonstrates decoding the returned attribute (in the wrattr predicate), by means of bitwise testing.