2 * DOS file system functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
16 #if defined(__svr4__) || defined(_SCO_DS)
17 #include <sys/statfs.h>
28 /* Chars we don't want to see in DOS file names */
29 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
31 static const char *DOSFS_Devices[][2] =
35 { "NUL", "/dev/null" },
47 #define GET_DRIVE(path) \
48 (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
50 /* DOS extended error status */
51 WORD DOS_ExtendedError;
57 /***********************************************************************
60 * Return 1 if Unix file 'name' is also a valid MS-DOS name
61 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
62 * File name can be terminated by '\0', '\\' or '/'.
64 static int DOSFS_ValidDOSName( const char *name )
66 static const char invalid_chars[] = INVALID_DOS_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
72 /* Check for "." and ".." */
75 /* All other names beginning with '.' are invalid */
76 return (IS_END_OF_NAME(*p));
78 while (!IS_END_OF_NAME(*p))
80 if (strchr( invalid_chars, *p )) return 0; /* Invalid char */
81 if (*p == '.') break; /* Start of the extension */
82 if (++len > 8) return 0; /* Name too long */
85 if (*p != '.') return 1; /* End of name */
87 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
89 while (!IS_END_OF_NAME(*p))
91 if (strchr( invalid_chars, *p )) return 0; /* Invalid char */
92 if (*p == '.') return 0; /* Second extension not allowed */
93 if (++len > 3) return 0; /* Extension too long */
100 /***********************************************************************
103 * Remove all '.' and '..' at the beginning of 'name'.
105 static const char * DOSFS_CheckDotDot( const char *name, char *buffer,
106 char sep , int *len )
108 char *p = buffer + strlen(buffer);
112 if (IS_END_OF_NAME(name[1]))
115 while ((*name == '\\') || (*name == '/')) name++;
117 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
120 while ((*name == '\\') || (*name == '/')) name++;
121 while ((p > buffer) && (*p != sep)) { p--; (*len)++; }
122 *p = '\0'; /* Remove trailing separator */
130 /***********************************************************************
131 * DOSFS_ToDosFCBFormat
133 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
134 * expanding wild cards and converting to upper-case in the process.
135 * File name can be terminated by '\0', '\\' or '/'.
136 * Return NULL if the name is not a valid DOS name.
138 const char *DOSFS_ToDosFCBFormat( const char *name )
140 static const char invalid_chars[] = INVALID_DOS_CHARS;
141 static char buffer[12];
142 const char *p = name;
145 /* Check for "." and ".." */
149 strcpy( buffer, ". " );
151 return (!*p || (*p == '/') || (*p == '\\')) ? buffer : NULL;
154 for (i = 0; i < 8; i++)
171 if (strchr( invalid_chars, *p )) return NULL;
172 buffer[i] = toupper(*p);
180 /* Skip all chars after wildcard up to first dot */
181 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
185 /* Check if name too long */
186 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return NULL;
188 if (*p == '.') p++; /* Skip dot */
190 for (i = 8; i < 11; i++)
200 return NULL; /* Second extension not allowed */
208 if (strchr( invalid_chars, *p )) return NULL;
209 buffer[i] = toupper(*p);
219 /***********************************************************************
220 * DOSFS_ToDosDTAFormat
222 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
223 * converting to upper-case in the process.
224 * File name can be terminated by '\0', '\\' or '/'.
225 * Return NULL if the name is not a valid DOS name.
227 const char *DOSFS_ToDosDTAFormat( const char *name )
229 static char buffer[13];
232 memcpy( buffer, name, 8 );
233 for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
235 memcpy( p, name + 8, 3 );
236 for (p += 3; p[-1] == ' '; p--);
237 if (p[-1] == '.') p--;
243 /***********************************************************************
246 * Check a DOS file name against a mask (both in FCB format).
248 static int DOSFS_Match( const char *mask, const char *name )
251 for (i = 11; i > 0; i--, mask++, name++)
252 if ((*mask != '?') && (*mask != *name)) return 0;
257 /***********************************************************************
258 * DOSFS_ToDosDateTime
260 * Convert a Unix time in the DOS date/time format.
262 void DOSFS_ToDosDateTime( time_t unixtime, WORD *pDate, WORD *pTime )
264 struct tm *tm = localtime( &unixtime );
266 *pTime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
268 *pDate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
273 /***********************************************************************
276 * Transform a Unix file name into a hashed DOS name. If the name is a valid
277 * DOS name, it is converted to upper-case; otherwise it is replaced by a
278 * hashed version that fits in 8.3 format.
279 * File name can be terminated by '\0', '\\' or '/'.
281 static const char *DOSFS_Hash( const char *name, int dir_format )
283 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
284 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
286 static char buffer[13];
292 if (dir_format) strcpy( buffer, " " );
294 if (DOSFS_ValidDOSName( name ))
296 /* Check for '.' and '..' */
300 if (!dir_format) buffer[1] = buffer[2] = '\0';
301 if (name[1] == '.') buffer[1] = '.';
305 /* Simply copy the name, converting to uppercase */
307 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
308 *dst++ = toupper(*name);
311 if (dir_format) dst = buffer + 8;
313 for (name++; !IS_END_OF_NAME(*name); name++)
314 *dst++ = toupper(*name);
316 if (!dir_format) *dst = '\0';
320 /* Compute the hash code of the file name */
321 /* If you know something about hash functions, feel free to */
322 /* insert a better algorithm here... */
323 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
324 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
325 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
327 /* Find last dot for start of the extension */
328 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
329 if (*p == '.') ext = p;
330 if (ext && IS_END_OF_NAME(ext[1]))
331 ext = NULL; /* Empty extension ignored */
333 /* Copy first 4 chars, replacing invalid chars with '_' */
334 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
336 if (IS_END_OF_NAME(*p) || (p == ext)) break;
337 *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
339 /* Pad to 5 chars with '~' */
340 while (i-- >= 0) *dst++ = '~';
342 /* Insert hash code converted to 3 ASCII chars */
343 *dst++ = hash_chars[(hash >> 10) & 0x1f];
344 *dst++ = hash_chars[(hash >> 5) & 0x1f];
345 *dst++ = hash_chars[hash & 0x1f];
347 /* Copy the first 3 chars of the extension (if any) */
350 if (!dir_format) *dst++ = '.';
351 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
352 *dst++ = toupper(*ext);
354 if (!dir_format) *dst = '\0';
360 /***********************************************************************
363 * Find the Unix file name in a given directory that corresponds to
364 * a file name (either in Unix or DOS format).
365 * File name can be terminated by '\0', '\\' or '/'.
366 * Return 1 if OK, 0 if no file name matches.
368 static int DOSFS_FindUnixName( const char *path, const char *name,
369 char *buffer, int maxlen )
372 struct dirent *dirent;
374 const char *dos_name = DOSFS_ToDosFCBFormat( name );
375 const char *p = strchr( name, '/' );
376 int len = p ? (int)(p - name) : strlen(name);
378 dprintf_dosfs( stddeb, "DOSFS_FindUnixName: %s %s\n", path, name );
380 if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
382 if (!(dir = opendir( path )))
384 dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s): can't open dir\n",
388 while ((dirent = readdir( dir )) != NULL)
390 /* Check against Unix name */
391 if ((len == strlen(dirent->d_name) &&
392 !memcmp( dirent->d_name, name, len ))) break;
395 /* Check against hashed DOS name */
396 const char *hash_name = DOSFS_Hash( dirent->d_name, TRUE );
397 if (!strcmp( dos_name, hash_name )) break;
400 if (dirent) lstrcpyn( buffer, dirent->d_name, maxlen );
402 dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s) -> %s\n",
403 path, name, dirent ? buffer : "** Not found **" );
404 return (dirent != NULL);
408 /***********************************************************************
411 * Check if a DOS file name represents a DOS device. Returns the name
412 * of the associated Unix device, or NULL if not found.
414 const char *DOSFS_IsDevice( const char *name )
419 if (name[0] && (name[1] == ':')) name += 2;
420 if ((p = strrchr( name, '/' ))) name = p + 1;
421 if ((p = strrchr( name, '\\' ))) name = p + 1;
422 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
424 const char *dev = DOSFS_Devices[i][0];
425 if (!lstrncmpi( dev, name, strlen(dev) ))
427 p = name + strlen( dev );
428 if (!*p || (*p == '.')) return DOSFS_Devices[i][1];
435 /***********************************************************************
436 * DOSFS_GetUnixFileName
438 * Convert a file name (DOS or mixed DOS/Unix format) to a valid Unix name.
439 * Return NULL if one of the path components does not exist. The last path
440 * component is only checked if 'check_last' is non-zero.
442 const char * DOSFS_GetUnixFileName( const char * name, int check_last )
444 static char buffer[MAX_PATHNAME_LEN];
445 int drive, len, found;
448 dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: %s\n", name );
449 if (name[0] && (name[1] == ':'))
451 drive = toupper(name[0]) - 'A';
454 else if (name[0] == '/') /* Absolute Unix path? */
456 if ((drive = DRIVE_FindDriveRoot( &name )) == -1)
458 fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
460 /* Assume it really was a DOS name */
461 drive = DRIVE_GetCurrentDrive();
464 else drive = DRIVE_GetCurrentDrive();
466 if (!DRIVE_IsValid(drive))
468 DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
471 lstrcpyn( buffer, DRIVE_GetRoot(drive), MAX_PATHNAME_LEN );
472 if (buffer[1]) root = buffer + strlen(buffer);
473 else root = buffer; /* root directory */
475 if ((*name == '\\') || (*name == '/'))
477 while ((*name == '\\') || (*name == '/')) name++;
481 lstrcpyn( root + 1, DRIVE_GetUnixCwd(drive),
482 MAX_PATHNAME_LEN - (int)(root - buffer) - 1 );
483 if (root[1]) *root = '/';
486 p = buffer[1] ? buffer + strlen(buffer) : buffer;
487 len = MAX_PATHNAME_LEN - strlen(buffer);
489 while (*name && found)
491 const char *newname = DOSFS_CheckDotDot( name, root, '/', &len );
494 p = root + strlen(root);
500 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
503 if ((found = DOSFS_FindUnixName( buffer, name, p+1, len-1 )))
508 while (!IS_END_OF_NAME(*name)) name++;
510 else if (!check_last)
513 for (len--; !IS_END_OF_NAME(*name) && (len > 1); name++, len--)
514 *p++ = tolower(*name);
517 while ((*name == '\\') || (*name == '/')) name++;
523 DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk );
526 if (*name) /* Not last */
528 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
532 if (!buffer[0]) strcpy( buffer, "/" );
533 dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: returning %s\n", buffer );
538 /***********************************************************************
539 * DOSFS_GetDosTrueName
541 * Convert a file name (DOS or Unix format) to a complete DOS name.
542 * Return NULL if the path name is invalid or too long.
543 * The unix_format flag is a hint that the file name is in Unix format.
545 const char * DOSFS_GetDosTrueName( const char *name, int unix_format )
547 static char buffer[MAX_PATHNAME_LEN];
551 dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName(%s,%d)\n", name, unix_format);
552 if (name[0] && (name[1] == ':'))
554 drive = toupper(name[0]) - 'A';
557 else if (name[0] == '/') /* Absolute Unix path? */
559 if ((drive = DRIVE_FindDriveRoot( &name )) == -1)
561 fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
563 /* Assume it really was a DOS name */
564 drive = DRIVE_GetCurrentDrive();
567 else drive = DRIVE_GetCurrentDrive();
569 if (!DRIVE_IsValid(drive))
571 DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
578 if (IS_END_OF_NAME(*name))
580 while ((*name == '\\') || (*name == '/')) name++;
585 lstrcpyn( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 );
586 if (*p) p += strlen(p); else p--;
589 len = MAX_PATHNAME_LEN - (int)(p - buffer);
593 const char *newname = DOSFS_CheckDotDot( name, buffer+2, '\\', &len );
596 p = buffer + strlen(buffer);
602 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
606 if (unix_format) /* Hash it into a DOS name */
608 lstrcpyn( p, DOSFS_Hash( name, FALSE ), len );
611 while (!IS_END_OF_NAME(*name)) name++;
613 else /* Already DOS format, simply upper-case it */
615 while (!IS_END_OF_NAME(*name) && (len > 1))
617 *p++ = toupper(*name);
623 while ((*name == '\\') || (*name == '/')) name++;
630 dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName: returning %s\n", buffer );
635 /***********************************************************************
638 * Find the next matching file. Return the number of entries read to find
639 * the matching one, or 0 if no more entries.
641 int DOSFS_FindNext( const char *path, const char *mask, int drive,
642 BYTE attr, int skip, DOS_DIRENT *entry )
644 static DIR *dir = NULL;
645 struct dirent *dirent;
647 static char buffer[MAX_PATHNAME_LEN];
648 static int cur_pos = 0;
649 static int drive_root = 0;
651 const char *hash_name;
653 if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
656 strcpy( entry->name, DRIVE_GetLabel( drive ) );
657 entry->attr = FA_LABEL;
659 DOSFS_ToDosDateTime( time(NULL), &entry->date, &entry->time );
663 /* Check the cached directory */
664 if (dir && !strcmp( buffer, path ) && (cur_pos <= skip)) skip -= cur_pos;
665 else /* Not in the cache, open it anew */
667 const char *drive_path;
668 dprintf_dosfs( stddeb, "DOSFS_FindNext: cache miss, path=%s skip=%d buf=%s cur=%d\n",
669 path, skip, buffer, cur_pos );
671 if (dir) closedir(dir);
672 if (!(dir = opendir( path ))) return 0;
675 if (DRIVE_FindDriveRoot( &drive_path ) != -1)
677 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
678 if (!*drive_path) drive_root = 1;
680 dprintf_dosfs(stddeb, "DOSFS_FindNext: drive_root = %d\n", drive_root);
681 lstrcpyn( buffer, path, sizeof(buffer) - 1 );
684 strcat( buffer, "/" );
685 p = buffer + strlen(buffer);
686 attr |= FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
688 while ((dirent = readdir( dir )) != NULL)
690 if (skip-- > 0) continue;
692 hash_name = DOSFS_Hash( dirent->d_name, TRUE );
693 if (!DOSFS_Match( mask, hash_name )) continue;
694 /* Don't return '.' and '..' in the root of the drive */
695 if (drive_root && (dirent->d_name[0] == '.') &&
696 (!dirent->d_name[1] ||
697 ((dirent->d_name[1] == '.') && !dirent->d_name[2]))) continue;
698 lstrcpyn( p, dirent->d_name, sizeof(buffer) - (int)(p - buffer) );
700 if (!FILE_Stat( buffer, &entry->attr, &entry->size,
701 &entry->date, &entry->time ))
703 fprintf( stderr, "DOSFS_FindNext: can't stat %s\n", buffer );
706 if (entry->attr & ~attr) continue;
707 strcpy( entry->name, hash_name );
708 lstrcpyn( entry->unixname, dirent->d_name, sizeof(entry->unixname) );
709 dprintf_dosfs( stddeb, "DOSFS_FindNext: returning %s %02x %ld\n",
710 entry->name, entry->attr, entry->size );
712 p[-1] = '\0'; /* Remove trailing slash in buffer */
717 return 0; /* End of directory */