Skip to content
Snippets Groups Projects
dos_fs.c 74.2 KiB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
/*
 * DOS file system functions
 *
 * Copyright 1993 Erik Bos
 * Copyright 1996 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Alexandre Julliard's avatar
Alexandre Julliard committed
 */

Alexandre Julliard's avatar
Alexandre Julliard committed
#include "config.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <sys/types.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <ctype.h>
#include <dirent.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <errno.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <sys/errno.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <fcntl.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_IOCTL_H
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <sys/ioctl.h>
#endif
Alexandre Julliard's avatar
Alexandre Julliard committed
#include <time.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
Alexandre Julliard's avatar
Alexandre Julliard committed

#define NONAMELESSUNION
#define NONAMELESSSTRUCT
Alexandre Julliard's avatar
Alexandre Julliard committed
#include "winerror.h"
#include "wingdi.h"

#include "wine/unicode.h"
#include "wine/winbase16.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
#include "drive.h"
#include "file.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
#include "heap.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
#include "msdos.h"
#include "wine/server.h"
Alexandre Julliard's avatar
Alexandre Julliard committed

WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
WINE_DECLARE_DEBUG_CHANNEL(file);
Alexandre Julliard's avatar
Alexandre Julliard committed
/* Define the VFAT ioctl to get both short and long file names */
Alexandre Julliard's avatar
Alexandre Julliard committed
/* FIXME: is it possible to get this to work on other systems? */
Alexandre Julliard's avatar
Alexandre Julliard committed
#ifdef linux
Alexandre Julliard's avatar
Alexandre Julliard committed
/* We want the real kernel dirent structure, not the libc one */
typedef struct
{
    long d_ino;
    long d_off;
    unsigned short d_reclen;
    char d_name[256];
} KERNEL_DIRENT;

#define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )

/* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/
#ifndef O_DIRECTORY
# define O_DIRECTORY    0200000	/* must be directory */
#endif

Alexandre Julliard's avatar
Alexandre Julliard committed
#else   /* linux */
#undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
#endif  /* linux */
Alexandre Julliard's avatar
Alexandre Julliard committed

#define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')

Alexandre Julliard's avatar
Alexandre Julliard committed
/* Chars we don't want to see in DOS file names */
Alexandre Julliard's avatar
Alexandre Julliard committed
#define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
static const DOS_DEVICE DOSFS_Devices[] =
/* name, device flags (see Int 21/AX=0x4400) */
{
    { {'C','O','N',0}, 0xc0d3 },
    { {'P','R','N',0}, 0xa0c0 },
    { {'N','U','L',0}, 0x80c4 },
    { {'A','U','X',0}, 0x80c0 },
    { {'L','P','T','1',0}, 0xa0c0 },
    { {'L','P','T','2',0}, 0xa0c0 },
    { {'L','P','T','3',0}, 0xa0c0 },
    { {'L','P','T','4',0}, 0xc0d3 },
    { {'C','O','M','1',0}, 0x80c0 },
    { {'C','O','M','2',0}, 0x80c0 },
    { {'C','O','M','3',0}, 0x80c0 },
    { {'C','O','M','4',0}, 0x80c0 },
    { {'S','C','S','I','M','G','R','$',0}, 0xc0c0 },
    { {'H','P','S','C','A','N',0}, 0xc0c0 },
    { {'E','M','M','X','X','X','X','0',0}, 0x0000 }
static const WCHAR devW[] = {'\\','D','e','v','i','c','e','\\',0};
static const WCHAR dosW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\',0};

static const WCHAR auxW[] = {'A','U','X',0};
static const WCHAR comW[] = {'C','O','M',0};
static const WCHAR lptW[] = {'L','P','T',0};
static const WCHAR nulW[] = {'N','U','L',0};

static const WCHAR nullW[] = {'N','u','l','l',0};
static const WCHAR parW[] = {'P','a','r','a','l','l','e','l',0};
static const WCHAR serW[] = {'S','e','r','i','a','l',0};
static const WCHAR oneW[] = {'1',0};

/*
 * Directory info for DOSFS_ReadDir
 * contains the names of *all* the files in the directory
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
typedef struct
{
    WCHAR names[1];
Alexandre Julliard's avatar
Alexandre Julliard committed
} DOS_DIR;

Alexandre Julliard's avatar
Alexandre Julliard committed
/* Info structure for FindFirstFile handle */
typedef struct
{
    char *path; /* unix path */
    LPWSTR long_mask;
    LPWSTR short_mask;
Alexandre Julliard's avatar
Alexandre Julliard committed
    BYTE  attr;
    int   drive;
    int   cur_pos;
    union
    {
        DOS_DIR *dos_dir;
        SMB_DIR *smb_dir;
    } u;
Alexandre Julliard's avatar
Alexandre Julliard committed
} FIND_FIRST_INFO;


static WINE_EXCEPTION_FILTER(page_fault)
{
    if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
        return EXCEPTION_EXECUTE_HANDLER;
    return EXCEPTION_CONTINUE_SEARCH;
}

Alexandre Julliard's avatar
Alexandre Julliard committed

/***********************************************************************
 *           DOSFS_ValidDOSName
 *
 * Return 1 if Unix file 'name' is also a valid MS-DOS name
 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
 * File name can be terminated by '\0', '\\' or '/'.
 */
static int DOSFS_ValidDOSName( LPCWSTR name, int ignore_case )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
Alexandre Julliard's avatar
Alexandre Julliard committed
    static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
    const WCHAR *p = name;
Alexandre Julliard's avatar
Alexandre Julliard committed
    const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
Alexandre Julliard's avatar
Alexandre Julliard committed
    int len = 0;

    if (*p == '.')
    {
        /* Check for "." and ".." */
        p++;
        if (*p == '.') p++;
        /* All other names beginning with '.' are invalid */
        return (IS_END_OF_NAME(*p));
    }
    while (!IS_END_OF_NAME(*p))
    {
        if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (*p == '.') break;  /* Start of the extension */
        if (++len > 8) return 0;  /* Name too long */
        p++;
    }
    if (*p != '.') return 1;  /* End of name */
    p++;
    if (IS_END_OF_NAME(*p)) return 0;  /* Empty extension not allowed */
    len = 0;
    while (!IS_END_OF_NAME(*p))
    {
        if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (*p == '.') return 0;  /* Second extension not allowed */
        if (++len > 3) return 0;  /* Extension too long */
        p++;
    }
    return 1;
}


/***********************************************************************
 *           DOSFS_ToDosFCBFormat
 *
 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
 * expanding wild cards and converting to upper-case in the process.
 * File name can be terminated by '\0', '\\' or '/'.
Alexandre Julliard's avatar
Alexandre Julliard committed
 * Return FALSE if the name is not a valid DOS name.
 * 'buffer' must be at least 12 characters long.
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
BOOL DOSFS_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
    static const char invalid_chars[] = INVALID_DOS_CHARS;
    LPCWSTR p = name;
Alexandre Julliard's avatar
Alexandre Julliard committed
    int i;

    /* Check for "." and ".." */
    if (*p == '.')
    {
        p++;
        buffer[0] = '.';
        for(i = 1; i < 11; i++) buffer[i] = ' ';
        buffer[11] = 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (*p == '.')
        {
            buffer[1] = '.';
            p++;
        }
Alexandre Julliard's avatar
Alexandre Julliard committed
        return (!*p || (*p == '/') || (*p == '\\'));
Alexandre Julliard's avatar
Alexandre Julliard committed
    }

    for (i = 0; i < 8; i++)
    {
        switch(*p)
        {
        case '\0':
        case '\\':
        case '/':
        case '.':
            buffer[i] = ' ';
            break;
        case '?':
            p++;
            /* fall through */
        case '*':
            buffer[i] = '?';
            break;
        default:
            if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
            buffer[i] = toupperW(*p);
Alexandre Julliard's avatar
Alexandre Julliard committed
            p++;
            break;
        }
    }

    if (*p == '*')
    {
        /* Skip all chars after wildcard up to first dot */
        while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
    }
    else
    {
        /* Check if name too long */
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
    if (*p == '.') p++;  /* Skip dot */

    for (i = 8; i < 11; i++)
    {
        switch(*p)
        {
        case '\0':
        case '\\':
        case '/':
            buffer[i] = ' ';
            break;
        case '.':
Alexandre Julliard's avatar
Alexandre Julliard committed
            return FALSE;  /* Second extension not allowed */
Alexandre Julliard's avatar
Alexandre Julliard committed
        case '?':
            p++;
            /* fall through */
        case '*':
            buffer[i] = '?';
            break;
        default:
            if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
            buffer[i] = toupperW(*p);
Alexandre Julliard's avatar
Alexandre Julliard committed
            p++;
            break;
        }
    }
    buffer[11] = '\0';

    /* at most 3 character of the extension are processed
     * is something behind this ?
    while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
Alexandre Julliard's avatar
Alexandre Julliard committed
}


/***********************************************************************
 *           DOSFS_ToDosDTAFormat
 *
 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
 * converting to upper-case in the process.
 * File name can be terminated by '\0', '\\' or '/'.
Alexandre Julliard's avatar
Alexandre Julliard committed
 * 'buffer' must be at least 13 characters long.
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
static void DOSFS_ToDosDTAFormat( LPCWSTR name, LPWSTR buffer )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
Alexandre Julliard's avatar
Alexandre Julliard committed

    memcpy( buffer, name, 8 * sizeof(WCHAR) );
    p = buffer + 8;
    while ((p > buffer) && (p[-1] == ' ')) p--;
Alexandre Julliard's avatar
Alexandre Julliard committed
    *p++ = '.';
    memcpy( p, name + 8, 3 * sizeof(WCHAR) );
Alexandre Julliard's avatar
Alexandre Julliard committed
    if (p[-1] == '.') p--;
    *p = '\0';
}


/***********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
 *           DOSFS_MatchShort
Alexandre Julliard's avatar
Alexandre Julliard committed
 *
 * Check a DOS file name against a mask (both in FCB format).
 */
static int DOSFS_MatchShort( LPCWSTR mask, LPCWSTR name )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
    int i;
    for (i = 11; i > 0; i--, mask++, name++)
        if ((*mask != '?') && (*mask != *name)) return 0;
    return 1;
}


Alexandre Julliard's avatar
Alexandre Julliard committed
/***********************************************************************
 *           DOSFS_MatchLong
 *
 * Check a long file name against a mask.
 *
 * Tests (done in W95 DOS shell - case insensitive):
 * *.txt			test1.test.txt				*
 * *st1*			test1.txt				*
 * *.t??????.t*			test1.ta.tornado.txt			*
 * *tornado*			test1.ta.tornado.txt			*
 * t*t				test1.ta.tornado.txt			*
 * ?est*			test1.txt				*
 * ?est???			test1.txt				-
 * *test1.txt*			test1.txt				*
 * h?l?o*t.dat			hellothisisatest.dat			*
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
static int DOSFS_MatchLong( LPCWSTR mask, LPCWSTR name, int case_sensitive )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
    LPCWSTR lastjoker = NULL;
    LPCWSTR next_to_retry = NULL;
    static const WCHAR asterisk_dot_asterisk[] = {'*','.','*',0};
    TRACE("(%s, %s, %x)\n", debugstr_w(mask), debugstr_w(name), case_sensitive);

    if (!strcmpW( mask, asterisk_dot_asterisk )) return 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
    while (*name && *mask)
    {
        if (*mask == '*')
        {
            mask++;
            while (*mask == '*') mask++;  /* Skip consecutive '*' */
            lastjoker = mask;
            if (!*mask) return 1; /* end of mask is all '*', so match */

            /* skip to the next match after the joker(s) */
Alexandre Julliard's avatar
Alexandre Julliard committed
            if (case_sensitive) while (*name && (*name != *mask)) name++;
            else while (*name && (toupperW(*name) != toupperW(*mask))) name++;
Alexandre Julliard's avatar
Alexandre Julliard committed
        }
        else if (*mask != '?')
        {
Alexandre Julliard's avatar
Alexandre Julliard committed
            if (case_sensitive)
            {
                if (toupperW(*mask) != toupperW(*name)) mismatch = 1;
            }
            if (!mismatch)
            {
                mask++;
                name++;
                if (*mask == '\0')
                {
                    if (*name == '\0')
                        return 1;
                    if (lastjoker)
                        mask = lastjoker;
                }
            }
            else /* mismatch ! */
            {
                if (lastjoker) /* we had an '*', so we can try unlimitedly */
                {
                    mask = lastjoker;

                    /* this scan sequence was a mismatch, so restart
                     * 1 char after the first char we checked last time */
                    next_to_retry++;
                    name = next_to_retry;
                }
                else
                    return 0; /* bad luck */
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
    while ((*mask == '.') || (*mask == '*'))
        mask++;  /* Ignore trailing '.' or '*' in mask */
Alexandre Julliard's avatar
Alexandre Julliard committed
    return (!*name && !*mask);
}


Alexandre Julliard's avatar
Alexandre Julliard committed
/***********************************************************************
 *           DOSFS_AddDirEntry
 *
 *  Used to construct an array of filenames in DOSFS_OpenDir
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
static BOOL DOSFS_AddDirEntry(DOS_DIR **dir, LPCWSTR name, LPCWSTR dosname)
Alexandre Julliard's avatar
Alexandre Julliard committed
{
    int extra1 = strlenW(name) + 1;
    int extra2 = strlenW(dosname) + 1;

    /* if we need more, at minimum double the size */
    if( (extra1 + extra2 + (*dir)->used) > (*dir)->size)
Alexandre Julliard's avatar
Alexandre Julliard committed
    {
        int more = (*dir)->size;
        DOS_DIR *t;

        if(more<(extra1+extra2))
            more = extra1+extra2;

        t = HeapReAlloc(GetProcessHeap(), 0, *dir, sizeof(**dir) + 
                        ((*dir)->size + more)*sizeof(WCHAR) );
        if(!t)
        {
            SetLastError( ERROR_NOT_ENOUGH_MEMORY );
            ERR("Out of memory caching directory structure %d %d %d\n",
                 (*dir)->size, more, (*dir)->used);
            return FALSE;
        }
        (*dir) = t;
        (*dir)->size += more;
    /* at this point, the dir structure is big enough to hold these names */
    strcpyW(&(*dir)->names[(*dir)->used], name);
    (*dir)->used += extra1;
    strcpyW(&(*dir)->names[(*dir)->used], dosname);
    (*dir)->used += extra2;

    return TRUE;
}
Alexandre Julliard's avatar
Alexandre Julliard committed


/***********************************************************************
 *           DOSFS_OpenDir_VFAT
 */
static BOOL DOSFS_OpenDir_VFAT(DOS_DIR **dir, const char *unix_path)
Alexandre Julliard's avatar
Alexandre Julliard committed
#ifdef VFAT_IOCTL_READDIR_BOTH
    KERNEL_DIRENT de[2];
    int fd = open( unix_path, O_RDONLY|O_DIRECTORY );
Alexandre Julliard's avatar
Alexandre Julliard committed

    /* Check if the VFAT ioctl is supported on this directory */

    if ( fd<0 )
        return FALSE;

    while (1)
Alexandre Julliard's avatar
Alexandre Julliard committed
    {
        WCHAR long_name[MAX_PATH];
        WCHAR short_name[12];

        r = (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1);
        if(!r)
            break;
        if (!de[0].d_reclen)
            break;
        MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
        if (!DOSFS_ToDosFCBFormat( long_name, short_name ))
            short_name[0] = '\0';
        if (de[1].d_name[0])
            MultiByteToWideChar(CP_UNIXCP, 0, de[1].d_name, -1, long_name, MAX_PATH);
Alexandre Julliard's avatar
Alexandre Julliard committed
        else
            MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
        r = DOSFS_AddDirEntry(dir, long_name, short_name );
        if(!r)
            break;
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
    if(r)
    {
        static const WCHAR empty_strW[] = { 0 };
        DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
    }
    close(fd);
    return r;
#else
    return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
#endif  /* VFAT_IOCTL_READDIR_BOTH */
}


/***********************************************************************
 *           DOSFS_OpenDir_Normal
 *
 * Now use the standard opendir/readdir interface
 */
static BOOL DOSFS_OpenDir_Normal( DOS_DIR **dir, const char *unix_path )
{
    DIR *unixdir = opendir( unix_path );
    BOOL r = TRUE;
    static const WCHAR empty_strW[] = { 0 };

    if(!unixdir)
        return FALSE;
    while(1)
    {
        WCHAR long_name[MAX_PATH];
        struct dirent *de = readdir(unixdir);

        if(!de)
            break;
        MultiByteToWideChar(CP_UNIXCP, 0, de->d_name, -1, long_name, MAX_PATH);
        r = DOSFS_AddDirEntry(dir, long_name, empty_strW);
        if(!r)
            break;
    }
    if(r)
        DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
    closedir(unixdir);
    return r;
}

/***********************************************************************
 *           DOSFS_OpenDir
 */
static DOS_DIR *DOSFS_OpenDir( const char *unix_path )
{
    const int init_size = 0x100;
    DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) + init_size*sizeof (WCHAR));
    BOOL r;

    TRACE("%s\n",debugstr_a(unix_path));

    if (!dir)
    {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return NULL;
    }
    dir->used = 0;
    dir->size = init_size;

    /* Treat empty path as root directory. This simplifies path split into
       directory and mask in several other places */
    if (!*unix_path) unix_path = "/";

    r = DOSFS_OpenDir_VFAT( &dir, unix_path);
Alexandre Julliard's avatar
Alexandre Julliard committed

        r = DOSFS_OpenDir_Normal( &dir, unix_path);
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    {
        HeapFree(GetProcessHeap(), 0, dir);
Alexandre Julliard's avatar
Alexandre Julliard committed
        return NULL;
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
    return dir;
}


/***********************************************************************
 *           DOSFS_CloseDir
 */
static void DOSFS_CloseDir( DOS_DIR *dir )
{
    HeapFree( GetProcessHeap(), 0, dir );
Alexandre Julliard's avatar
Alexandre Julliard committed
}


/***********************************************************************
 *           DOSFS_ReadDir
 */
static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCWSTR *long_name,
                             LPCWSTR *short_name )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
Alexandre Julliard's avatar
Alexandre Julliard committed

    if (!dir)
       return FALSE;

    /* the long pathname is first */
    ln = &dir->names[dir->used];
    if(ln[0])
        *long_name  = ln;
    else
        return FALSE;
    dir->used += (strlenW(ln) + 1);

    /* followed by the short path name */
    sn = &dir->names[dir->used];
    if(sn[0])
        *short_name = sn;
    else
        *short_name = NULL;
    dir->used += (strlenW(sn) + 1);
Alexandre Julliard's avatar
Alexandre Julliard committed
    return TRUE;
}


Alexandre Julliard's avatar
Alexandre Julliard committed
/***********************************************************************
 *           DOSFS_Hash
 *
 * Transform a Unix file name into a hashed DOS name. If the name is a valid
 * DOS name, it is converted to upper-case; otherwise it is replaced by a
 * hashed version that fits in 8.3 format.
 * File name can be terminated by '\0', '\\' or '/'.
Alexandre Julliard's avatar
Alexandre Julliard committed
 * 'buffer' must be at least 13 characters long.
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
static void DOSFS_Hash( LPCWSTR name, LPWSTR buffer, BOOL dir_format,
Alexandre Julliard's avatar
Alexandre Julliard committed
{
    static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
    static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";

    LPCWSTR p, ext;
    LPWSTR dst;
Alexandre Julliard's avatar
Alexandre Julliard committed
    unsigned short hash;
    int i;

    if (dir_format)
    {
        for(i = 0; i < 11; i++) buffer[i] = ' ';
        buffer[11] = 0;
    }
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    if (DOSFS_ValidDOSName( name, ignore_case ))
Alexandre Julliard's avatar
Alexandre Julliard committed
    {
        /* Check for '.' and '..' */
        if (*name == '.')
        {
            buffer[0] = '.';
            if (!dir_format) buffer[1] = buffer[2] = '\0';
            if (name[1] == '.') buffer[1] = '.';
Alexandre Julliard's avatar
Alexandre Julliard committed
            return;
Alexandre Julliard's avatar
Alexandre Julliard committed
        }

        /* Simply copy the name, converting to uppercase */

        for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
            *dst++ = toupperW(*name);
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (*name == '.')
        {
            if (dir_format) dst = buffer + 8;
            else *dst++ = '.';
            for (name++; !IS_END_OF_NAME(*name); name++)
                *dst++ = toupperW(*name);
Alexandre Julliard's avatar
Alexandre Julliard committed
        }
        if (!dir_format) *dst = '\0';
Alexandre Julliard's avatar
Alexandre Julliard committed
        return;
    }

    /* Compute the hash code of the file name */
    /* If you know something about hash functions, feel free to */
    /* insert a better algorithm here... */
    if (ignore_case)
    {
        for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
            hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
        hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
    else
    {
Alexandre Julliard's avatar
Alexandre Julliard committed
        for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
            hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
        hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
    }
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    /* Find last dot for start of the extension */
    for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
        if (*p == '.') ext = p;
    if (ext && IS_END_OF_NAME(ext[1]))
        ext = NULL;  /* Empty extension ignored */
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    /* Copy first 4 chars, replacing invalid chars with '_' */
    for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
    {
        if (IS_END_OF_NAME(*p) || (p == ext)) break;
        *dst++ = (*p < 256 && strchr( invalid_chars, (char)*p )) ? '_' : toupperW(*p);
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
    /* Pad to 5 chars with '~' */
    while (i-- >= 0) *dst++ = '~';
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    /* Insert hash code converted to 3 ASCII chars */
    *dst++ = hash_chars[(hash >> 10) & 0x1f];
    *dst++ = hash_chars[(hash >> 5) & 0x1f];
    *dst++ = hash_chars[hash & 0x1f];
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    /* Copy the first 3 chars of the extension (if any) */
    if (ext)
    {
        if (!dir_format) *dst++ = '.';
        for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
            *dst++ = (*ext < 256 && strchr( invalid_chars, (char)*ext )) ? '_' : toupperW(*ext);
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
    if (!dir_format) *dst = '\0';
Alexandre Julliard's avatar
Alexandre Julliard committed
}


/***********************************************************************
 *           DOSFS_FindUnixName
 *
 * Find the Unix file name in a given directory that corresponds to
 * a file name (either in Unix or DOS format).
 * File name can be terminated by '\0', '\\' or '/'.
Alexandre Julliard's avatar
Alexandre Julliard committed
 * Return TRUE if OK, FALSE if no file name matches.
 *
 * 'long_buf' must be at least 'long_len' characters long. If the long name
 * turns out to be larger than that, the function returns FALSE.
 * 'short_buf' must be at least 13 characters long.
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
BOOL DOSFS_FindUnixName( const DOS_FULL_NAME *path, LPCWSTR name, char *long_buf,
                         INT long_len, LPWSTR short_buf, BOOL ignore_case)
Alexandre Julliard's avatar
Alexandre Julliard committed
{
Alexandre Julliard's avatar
Alexandre Julliard committed
    DOS_DIR *dir;
    LPCWSTR long_name, short_name;
    WCHAR dos_name[12], tmp_buf[13];
Alexandre Julliard's avatar
Alexandre Julliard committed

    LPCWSTR p = strchrW( name, '/' );
    int len = p ? (int)(p - name) : strlenW(name);
    if ((p = strchrW( name, '\\' ))) len = min( (int)(p - name), len );
    /* Ignore trailing dots and spaces */
    while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
Alexandre Julliard's avatar
Alexandre Julliard committed
    if (long_len < len + 1) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed

    TRACE("%s,%s\n", path->long_name, debugstr_w(name) );
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
Alexandre Julliard's avatar
Alexandre Julliard committed

    if (!(dir = DOSFS_OpenDir( path->long_name )))
Alexandre Julliard's avatar
Alexandre Julliard committed
    {
        WARN("(%s,%s): can't open dir: %s\n",
             path->long_name, debugstr_w(name), strerror(errno) );
Alexandre Julliard's avatar
Alexandre Julliard committed
        return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
Alexandre Julliard's avatar
Alexandre Julliard committed

    while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
Alexandre Julliard's avatar
Alexandre Julliard committed
    {
        /* Check against Unix name */
        if (len == strlenW(long_name))
Alexandre Julliard's avatar
Alexandre Julliard committed
        {
Alexandre Julliard's avatar
Alexandre Julliard committed
            if (!ignore_case)
Alexandre Julliard's avatar
Alexandre Julliard committed
            {
                if (!strncmpW( long_name, name, len )) break;
Alexandre Julliard's avatar
Alexandre Julliard committed
            }
            else
            {
                if (!strncmpiW( long_name, name, len )) break;
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (dos_name[0])
Alexandre Julliard's avatar
Alexandre Julliard committed
        {
            /* Check against hashed DOS name */
Alexandre Julliard's avatar
Alexandre Julliard committed
            if (!short_name)
Alexandre Julliard's avatar
Alexandre Julliard committed
            {
                DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
                short_name = tmp_buf;
            }
            if (!strcmpW( dos_name, short_name )) break;
Alexandre Julliard's avatar
Alexandre Julliard committed
        }
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
    if (ret)
    {
        if (long_buf) WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, long_buf, long_len, NULL, NULL);
Alexandre Julliard's avatar
Alexandre Julliard committed
        if (short_buf)
        {
            if (short_name)
                DOSFS_ToDosDTAFormat( short_name, short_buf );
            else
                DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
        }
        TRACE("(%s,%s) -> %s (%s)\n", path->long_name, debugstr_w(name),
              debugstr_w(long_name), short_buf ? debugstr_w(short_buf) : "***");
Alexandre Julliard's avatar
Alexandre Julliard committed
    }
    else
        WARN("%s not found in '%s'\n", debugstr_w(name), path->long_name);
Alexandre Julliard's avatar
Alexandre Julliard committed
    DOSFS_CloseDir( dir );
    return ret;
Alexandre Julliard's avatar
Alexandre Julliard committed
}


/***********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
 *           DOSFS_GetDevice
Alexandre Julliard's avatar
Alexandre Julliard committed
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
 * Check if a DOS file name represents a DOS device and return the device.
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
const DOS_DEVICE *DOSFS_GetDevice( LPCWSTR name )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
Alexandre Julliard's avatar
Alexandre Julliard committed

    if (!name) return NULL; /* if wine_server_handle_to_fd was used */
Alexandre Julliard's avatar
Alexandre Julliard committed
    if (name[0] && (name[1] == ':')) name += 2;
    if ((p = strrchrW( name, '/' ))) name = p + 1;
    if ((p = strrchrW( name, '\\' ))) name = p + 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
    for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
    {
        const WCHAR *dev = DOSFS_Devices[i].name;
        if (!strncmpiW( dev, name, strlenW(dev) ))
Alexandre Julliard's avatar
Alexandre Julliard committed
        {
            p = name + strlenW( dev );
            if (!*p || (*p == '.') || (*p == ':')) return &DOSFS_Devices[i];
Alexandre Julliard's avatar
Alexandre Julliard committed
    return NULL;

/***********************************************************************
 *           DOSFS_GetDeviceByHandle
 */
const DOS_DEVICE *DOSFS_GetDeviceByHandle( HANDLE hFile )
            if ((reply->id >= 0) &&
                (reply->id < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
                ret = &DOSFS_Devices[reply->id];
/**************************************************************************
 *         DOSFS_CreateCommPort
 */
static HANDLE DOSFS_CreateCommPort(LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa)
    HKEY hkey;
    DWORD dummy;
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING nameW;
    WCHAR *devnameW;
    char tmp[128];
    char devname[40];

    static const WCHAR serialportsW[] = {'M','a','c','h','i','n','e','\\',
                                         'S','o','f','t','w','a','r','e','\\',
                                         'W','i','n','e','\\','W','i','n','e','\\',
                                         'C','o','n','f','i','g','\\',
                                         'S','e','r','i','a','l','P','o','r','t','s',0};
    TRACE_(file)("%s %lx %lx\n", debugstr_w(name), access, attributes);
    attr.Length = sizeof(attr);
    attr.RootDirectory = 0;
    attr.ObjectName = &nameW;
    attr.Attributes = 0;
    attr.SecurityDescriptor = NULL;
    attr.SecurityQualityOfService = NULL;
    RtlInitUnicodeString( &nameW, serialportsW );

    if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
    RtlInitUnicodeString( &nameW, name );
    if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
        devnameW = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
    else
        devnameW = NULL;

    NtClose( hkey );

    if (!devnameW) return 0;
    WideCharToMultiByte(CP_ACP, 0, devnameW, -1, devname, sizeof(devname), NULL, NULL);

    TRACE("opening %s as %s\n", devname, debugstr_w(name));
        req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle);
        req->attributes = attributes;
        req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
        wine_server_add_data( req, devname, strlen(devname) );
        wine_server_call_err( req );
        ret = reply->handle;
Andreas Mohr's avatar
Andreas Mohr committed
        ERR("Couldn't open device '%s' ! (check permissions)\n",devname);
        TRACE("return %p\n", ret );
Alexandre Julliard's avatar
Alexandre Julliard committed
/***********************************************************************
 *           DOSFS_OpenDevice
 *
 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
Alexandre Julliard's avatar
Alexandre Julliard committed
 */
HANDLE DOSFS_OpenDevice( LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa )
Alexandre Julliard's avatar
Alexandre Julliard committed
{
Alexandre Julliard's avatar
Alexandre Julliard committed

Alexandre Julliard's avatar
Alexandre Julliard committed
    if (name[0] && (name[1] == ':')) name += 2;
    if ((p = strrchrW( name, '/' ))) name = p + 1;
    if ((p = strrchrW( name, '\\' ))) name = p + 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
    for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
    {
        const WCHAR *dev = DOSFS_Devices[i].name;
        if (!strncmpiW( dev, name, strlenW(dev) ))
Alexandre Julliard's avatar
Alexandre Julliard committed
        {
            p = name + strlenW( dev );
            if (!*p || (*p == '.') || (*p == ':')) {
		static const WCHAR nulW[] = {'N','U','L',0};
		static const WCHAR conW[] = {'C','O','N',0};
		static const WCHAR scsimgrW[] = {'S','C','S','I','M','G','R','$',0};
		static const WCHAR hpscanW[] = {'H','P','S','C','A','N',0};
		static const WCHAR emmxxxx0W[] = {'E','M','M','X','X','X','X','0',0};
Alexandre Julliard's avatar
Alexandre Julliard committed
	    	/* got it */
		if (!strcmpiW(DOSFS_Devices[i].name, nulW))
                    return FILE_CreateFile( "/dev/null", access,
                                            FILE_SHARE_READ|FILE_SHARE_WRITE, sa,
                                            OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN );
		if (!strcmpiW(DOSFS_Devices[i].name, conW)) {
			switch (access & (GENERIC_READ|GENERIC_WRITE)) {
			case GENERIC_READ:
Alexandre Julliard's avatar
Alexandre Julliard committed
				to_dup = GetStdHandle( STD_INPUT_HANDLE );
Alexandre Julliard's avatar
Alexandre Julliard committed
				break;
Alexandre Julliard's avatar
Alexandre Julliard committed
				to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
Alexandre Julliard's avatar
Alexandre Julliard committed
				break;
			default:
				FIXME("can't open CON read/write\n");
Alexandre Julliard's avatar
Alexandre Julliard committed
			}
Alexandre Julliard's avatar
Alexandre Julliard committed
			if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
					      &handle, 0,
					      sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle,
					      DUPLICATE_SAME_ACCESS ))
Alexandre Julliard's avatar
Alexandre Julliard committed
			return handle;
Alexandre Julliard's avatar
Alexandre Julliard committed
		}
		if (!strcmpiW(DOSFS_Devices[i].name, scsimgrW) ||
                    !strcmpiW(DOSFS_Devices[i].name, hpscanW) ||
                    !strcmpiW(DOSFS_Devices[i].name, emmxxxx0W))
                    return FILE_CreateDevice( i, access, sa );
Alexandre Julliard's avatar
Alexandre Julliard committed
		}
                if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) )
                FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices[i].name));
Alexandre Julliard's avatar
Alexandre Julliard committed
	    }
Alexandre Julliard's avatar
Alexandre Julliard committed
        }