Copyright (c) Hyperion Entertainment and contributors.

Path Name Handling

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Description

When processing file names, it is often necessary to extract only a file name from a path or to generate an absolute path to a file. The dos.library contains several routines designed to make this easier.

FilePart() and PathPart()

The FilePart() function takes a pointer to a file path and returns a pointer to the last part of the string (the part of the string that follows the last separator character, "/"). The last part of the string is normally a file or directory name.

STRPTR FilePart(STRPTR mypath);

In case mypath (from the prototype above) consists only of a file or directory name (like startup-sequence, s, or libs), FilePart() returns a pointer to the start of the string (a.k.a. the same value as mypath).

The PathPart() function returns a pointer to the last character in the path string that doesn't include the file name (usually the separator character "/"):

STRPTR PathPart(STRPTR mypath);

If passed a pointer to the path string "sys:s/startup-sequence", PathPart() will return a pointer to "/startup-sequence". The application can then put a \0 where PathPart() points so that the original string has been shortened to a path part string. In case mypath (from the above prototype) is only a file name, PathPart() returns a pointer to this file name (the same value as mypath). This can confuse an application if it blindly expects PathPart() to return a path. An application should make sure the pointer PathPart() returns is not the same pointer the application passed to the function.

Both FilePart() and PathPart() consider the initial colon of an absolute path name (a path starting with a volume/device/assign name) to be a special separator character, but PathPart() will not include the colon in the string it returns a pointer to. For example, if passed the string "ram:tmpfile", PathPart() will return a pointer to the string "tmpfile".

Neither FilePart() nor PathPart() check the syntax of the path string passed to them, so if your application passes an invalid path to them, they will pass back equally invalid path fragments. Also, they do not process any of the wildcard tokens, treating them as normal characters. The following chart summarizes the possible results of the FilePart() and PathPart() functions. It assumes that the path supplied to the functions is valid.

wholepath is the path passed to FilePart() and PathPart()
pathpart is result of PathPart()
filepart is the result of FilePart()
If (wholepath != pathpart != filepart) then filepart points to the the file or directory name in wholepath and pathpart points to the '/' before the file or directory name in wholepath.
wholepath: "ram:t/tmpfile"
Filepart: "tmpfile"
Pathpart: "/tmpfile"
wholepath: "//t/tmpfile"
Filepart: "tmpfile"
Pathpart: "/tmpfile"
If (wholepath != pathpart == filepart) then filepart and pathpart point to a file or a directory name preceded by a volume name or series of separator characters.
wholepath: "ram:tmpfile"
Filepart: "tmpfile"
Pathpart: "tmpfile"
wholepath: "//tmpfile"
Filepart: "tmpfile"
Pathpart: "tmpfile"
If (wholepath == pathpart == filepart) then pathpart points to either a file or a directory name. This name is not preceded by a volume name.
wholepath: "tmpfile"
Filepart: "tmpfile"
Pathpart: "tmpfile"
If (pathpart[0] == 0) then path points to either "" (current directory), a series of separators ("/", "//", ":", etc.), or a volume/device name.
wholepath: "////"
Filepart: ""
Pathpart: ""
wholepath: "ram:"
Filepart: ""
Pathpart: ""

Example

// Part.c - File/Path separator example.
 
#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/rdargs.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
LONG  GetPath(STRPTR path, STRPTR buffer, LONG buffersize);
STRPTR ItsWild(CONST_STRPTR string);
 
int main()
{
    struct RDArgs  *readargs;
    LONG            rargs[2];
    LONG            vargs[8];
    STRPTR          path, filename;
    STRPTR          buffer;
    STRPTR          filepart, pathpart;
    struct Process *process;
    BPTR            lock;
    APTR            wptr;
    BOOL            error;
 
    /*
     * Use a generous 256 byte buffer. Should suffice for everything but
     * extreme cases.
     */
    if (buffer = IExec->AllocVecTags(256, AVT_ClearWithValue, 0, TAG_END))
    {
        if (readargs = IDOS->ReadArgs("PATH/A,FILENAME/A", rargs, NULL))
        {
            path = (UBYTE *) (rargs[0]);
            filename = (UBYTE *) (rargs[1]);
 
            error = GetPath(path, buffer, 255);
            if (error)
                IDOS->PrintFault(error, NULL);
 
            filepart = IDOS->FilePart(path);
            pathpart = IDOS->PathPart(path);
 
            vargs[0] = (LONG) path;
            vargs[1] = (LONG) filepart;
            vargs[2] = (LONG) pathpart;
            vargs[3] = (LONG) buffer;
            IDOS->VFPrintf(Output(),
                     "Filename: %s\nFilepart: %s\nPathpart: %s\nPath: %s\n",
                     vargs);
 
            /* No requesters */
            process = (struct Process *) IExec->FindTask(NULL);
            wptr = process->pr_WindowPtr;
            process->pr_WindowPtr = (APTR) - 1L;
 
            /*
             * Make sure this name is for real. This will weed out names
             * like "dh0:/" and non-existent directories. (and also
             * complain about non-mounted volumes.) It is tempting to look
             * for trailing slashes and remove them but you shouldn't. You
             * might misinterpret the users intention. Better to generate a
             * warning and prompt for new input.
             */
            if (lock = IDOS->Lock(buffer, SHARED_LOCK))
                IDOS->UnLock(lock);
            else
                IDOS->PrintFault(IDOS->IoErr(), buffer);
 
            /* Reset windowpointer */
            process->pr_WindowPtr = wptr;
 
            /*
             * Normally we should respect the test for an invalid path. To
             * show the results however, we blunder along...
             *
             * Add the filename to the path.
             */
            if (IDOS->AddPart(buffer, filename, 255))
                vargs[0] = (LONG) buffer;
            else
                vargs[0] = (LONG) "OVERFLOW";
 
            IDOS->VFPrintf(Output(), "\nNew path: %s\n", vargs);
 
            IDOS->FreeArgs(readargs);
        }
        else
            IDOS->PrintFault(IoErr(), NULL);
 
        IExec->FreeVec(buffer);
    }
    return 0;
}
 
/*
 * Standalone function to isolate a path and copy it into a supplied buffer.
 * Does not test if the path is valid. Returns an error in case of buffer
 * overflow.
 */
LONG
GetPath(STRPTR path, STRPTR buffer, LONG buffersize)
{
    STRPTR          pathpart, filepart;
    STRPTR          tmp1, tmp2;
    BPTR            lock;
    struct FileInfoBlock *fib;
    LONG            error = 0;
 
    /*
     * If there seems to be no path, the pathpart will point to the filepart
     * too, so we need to check for that.
     */
    filepart = IDOS->FilePart(path);
    pathpart = IDOS->PathPart(path);
 
    /*
     * This also handles cases where there is only a volume/device name, only a
     * directory name or a combo of those.
     */
    if (pathpart == path)
    {
 
        /*
         * There seems to be only one component. Copy it if it is not wild.
         * Caller will have to check whether if it exists and if it is a file
         * or directory.
         */
        if (!(ItsWild(pathpart)))
            pathpart = NULL;
    }
 
    if (pathpart != path)
    {
 
        /*
         * If pathpart equals filepart (pointer wise) then there is only one
         * component (possible preceeded by a volume name).
         */
        if (pathpart == filepart)
        {
            if (!(ItsWild(pathpart)))
                pathpart = NULL;
        }
        else
        {
 
            /*
             * Try to lock it to determine if the last component is a
             * directory.
             */
            if (lock = IDOS->Lock(path, SHARED_LOCK))
            {
                struct ExamineData *data = IDOS->ExamineObjectTags(
                    EX_FileLockInput, lock,
                    TAG_END);
 
                if (data != NULL && EXD_IS_DIRECTORY(data))
                {
                    /* Hey it's a directory after all */
                    pathpart = NULL;
                }
 
                IDOS->UnLock(lock);
            }           /* else treat it as a filename */
        }
 
        /* Copy the pathpart in the buffer */
        tmp1 = buffer;
        tmp2 = path;
        while ((*tmp1++ = *tmp2++) && (tmp2 != pathpart))
        {
            if (tmp1 == (buffer + buffersize))
            {
                error = ERROR_NO_FREE_STORE;
                break;
            }
        }
        *tmp1 = '\0';  /* NULL terminate. */
    }
 
    return (error);
}
 
/* Simple test whether a filename contains wildcards or not */
STRPTR
ItsWild(CONST_STRPTR string)
{
    static STRPTR special = "#?*%([|";
    STRPTR        tmp = string;
    COUNT         i;
 
    do
    {
        for (i = 0; special[i] != '\0'; i++)
        {
            if (*tmp == special[i])
                return (tmp);
        }
        tmp++;
    } while (*tmp);
 
    return (NULL);
}

AddPart()

The dos.library AddPart() function lets you append a file or directory name to the end of a (relative) path, inserting any necessary separator characters:

BOOL AddPart(STRPTR mypathname, STRPTR myfilename, uint32 mysize);

AddPart() will add myfilename to the end of mypathname. The mysize argument indicates how large the mypathname buffer is. In case this buffer is too small, AppPart() returns FALSE and makes no changes to the buffer. If myfilename is an absolute path, it will replace the current contents of the mypathname buffer. If myfilename starts with a colon, AddPart() will generate a new path relative to the root of the volume/device in mypathname.

SplitName()

Another dos.library function called SplitName() makes it possible to extract individual volume/device, directory, or file names (referred to here as path components) from a path string:

int16 = SplitName(STRPTR mypathname, uint8 separatorchar, STRPTR buffer, int16 oldposition, int32 buffersize);

SplitName() searches through the string mypathname for the separator character (separatorchar from the above prototype) starting at oldposition. As it is stepping through mypathname, SplitName() copies the characters it encounters from mypathname to the buffer, terminating the resulting string with a NULL. It does not copy the separator character into the buffer. If it finds the separator character, SplitName() returns the position of the character that follows the separator character. This position can be used as the oldposition argument in subsequent calls to SplitName() to extract other path components from the same path. If SplitName() does not find another separator character, it will return -1, although it will still copy characters from mypathname to buffer. For example, if SplitName() was called with the following arguments:

mypathname = "ram:env/sys/win.pat"
separatorchar = '/'
oldposition = 8
buffersize = 10

buffer would contain the NULL terminated string "sys" and SplitName() would return a value of 12. If SplitName() was called again placing the return value of 12 into oldposition, buffer would contain the NULL terminated string "win.pat" and SplitName() would return a value of -1, as there are no separator characters in mypathname beyond the eleventh character.

If SplitName() runs out of room in the buffer, it will copy as much of the characters as it can fit (buffersize - 1) and will write a NULL into the last position in the buffer. Also, if SplitName() runs out of room in the buffer and it finds another separator character, the position it returns will not be of the character that follows the separator character. Instead, SplitName() will return the position of the actual separator character.

Example

// Split.c - SplitName() example
 
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/rdargs.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
#define BUFFERSIZE  128
 
int main()
{
    struct RDArgs  *readargs;
    LONG            rargs[2];
    UBYTE          *filename, *buffer;
    ULONG           buffersize;
    WORD            position = 0;
    LONG            vargs[4];
 
    /* See the DOS Autodocs for more information about ReadArgs() */
    if (readargs = IDOS->ReadArgs("FILE/A,BUFFERSIZE/A/N", rargs, NULL))
    {
        filename = (UBYTE *) rargs[0];
        buffersize = *((LONG *) rargs[1]);
        if (buffersize < 1 || buffersize > BUFFERSIZE)
            buffersize = BUFFERSIZE;
 
        if (buffer = IExec->AllocVecTags(buffersize, AVT_ClearWithValue, 0, TAG_END))
        {
            position = IDOS->SplitName(filename, ':', buffer, position, buffersize);
 
            vargs[0] = position;
            vargs[1] = (LONG) buffer;
            IDOS->VFPrintf(IDOS->Output(), "Devicename: position: %ld Buffer: %s\n", vargs);
 
            if (position == -1)
                position = 0;
 
            do
            {
                position =
                    IDOS->SplitName(filename, '/', buffer, position, buffersize);
                vargs[0] = position;
                vargs[1] = (LONG) buffer;
                IDOS->VFPrintf(IDOS->Output(),
                         "Path component: position: %ld Buffer: %s\n",
                         vargs);
            } while (position != -1);
            IExec->FreeVec(buffer);
        }
        IDOS->FreeArgs(readargs);
    }
    else
        IDOS->PrintFault(IDOS->IoErr(), NULL);
 
    return 0;
}