rockbox/firmware/target/hosted/filesystem-win32.c
Solomon Peachy a2c10f6189 unicode: Support characters beyond the first unicode plane
We used 16-bit variables to store the 'character code' everywhere but
this won't let us represent anything beyond U+FFFF.

This patch changes those variables to a custom type that can be 32 or 16
bits depending on the build, and adjusts numerous internal APIs and
datastructures to match.  This includes:

 * utf8decode() and friends
 * font manipulation, caching, rendering, and generation
 * on-screen keyboard
 * FAT filesystem (parsing and generating utf16 LFNs)
 * WIN32 simulator platform code

Note that this patch doesn't _enable_ >16bit unicode support; a followup
patch will turn that on for appropriate targets.

Appears to work on:

  * hosted linux, native, linux simulator in both 16/32-bit modes.

Needs testing on:

  * windows and macos simulator (16bit+32bit)

Change-Id: Iba111b27d2433019b6bff937cf1ebd2c4353a0e8
2025-09-12 09:24:30 -04:00

524 lines
13 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 by Michael Sevakis
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define RB_FILESYSTEM_OS
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include "config.h"
#include "system.h"
#include "file.h"
#include "dir.h"
#include "debug.h"
#include "pathfuncs.h"
#include "string-extra.h"
#include "mv.h"
#define SAME_FILE_INFO(lpInfo1, lpInfo2) \
((lpInfo1)->dwVolumeSerialNumber == (lpInfo2)->dwVolumeSerialNumber && \
(lpInfo1)->nFileIndexHigh == (lpInfo2)->nFileIndexHigh && \
(lpInfo1)->nFileIndexLow == (lpInfo2)->nFileIndexLow)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
static void win32_last_error_errno(void)
{
switch (GetLastError())
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
errno = ENOENT;
break;
case ERROR_DIR_NOT_EMPTY:
errno = ENOTEMPTY;
break;
default:
errno = EIO;
}
}
#ifdef __MINGW32__
#include <wchar.h>
#include "rbunicode.h"
static HANDLE win32_open(const char *ospath);
static int win32_stat(const char *ospath, LPBY_HANDLE_FILE_INFORMATION lpInfo);
static unsigned short * strcpy_utf8utf16(unsigned short *buffer,
const unsigned char *utf8)
{
for (wchar_t *ucs = buffer; *ucs ; ucs++) {
ucschar_t cp;
utf8 = utf8decode(utf8, &cp);
#ifdef UNICODE32
if (cp > 0x10000) {
cp -= 0x10000;
*ucs++ = 0xd800 | (cp >> 10);
cp = 0xdc00 | (cp & 0x3ff);
}
#endif
*ucs = cp;
}
return buffer;
}
#if 0 /* Unused in current code */
static unsigned char * strcpy_utf16utf8(unsigned char *buffer,
const unsigned short *utf16buf)
{
unsigned char *utf8 = buffer;
/* windows is always LE */
const int le = 1;
while (*utf16buf) {
const unsigned char *utf16 = (const unsigned char *)utf16buf;
unsigned long ucs;
/* Check for a surrogate pair */
if (*(utf16 + le) >= 0xD8 && *(utf16 + le) < 0xE0) {
ucs = 0x10000 + ((utf16[1 - le] << 10) | ((utf16[le] - 0xD8) << 18)
| utf16[2 + (1 - le)] | ((utf16[2 + le] - 0xDC) << 8));
utf16buf += 2;
} else {
ucs = utf16[le] << 8 | utf16[1 - le];
utf16buf++;
}
utf8 = utf8encode(ucs, utf8);
}
return buffer;
}
static size_t strlen_utf16utf8(const unsigned short *utf16buf)
{
size_t length = 0;
unsigned char utf8char[4];
/* windows is always LE */
const int le = 1;
while (*utf16buf) {
const unsigned char *utf16 = (const unsigned char *)utf16buf;
unsigned long ucs;
/* Check for a surrogate pair */
if (*(utf16 + le) >= 0xD8 && *(utf16 + le) < 0xE0) {
ucs = 0x10000 + ((utf16[1 - le] << 10) | ((utf16[le] - 0xD8) << 18)
| utf16[2 + (1 - le)] | ((utf16[2 + le] - 0xDC) << 8));
utf16buf += 2;
} else {
ucs = utf16[le] << 8 | utf16[1 - le];
utf16buf++;
}
length += utf8encode(ucs, utf8char) - utf8char;
}
return length;
}
#endif
/* Note: Must be exported */
size_t strlcpy_utf16utf8(char *buffer, const unsigned short *utf16,
size_t bufsize)
{
if (!buffer)
bufsize = 0;
size_t length = 0;
unsigned char utf8char[4];
unsigned long ucc;
while(*utf16)
{
/* Check for a surrogate UTF16 pair */
if (*utf16 >= 0xd800 && *utf16 < 0xdc00 &&
*(utf16+1) >= 0xdc00 && *(utf16+1) < 0xe000) {
ucc = 0x10000 + (((*utf16 & 0x3ff) << 10) | (*(utf16+1) & 0x3ff));
utf16++;
} else {
ucc = *utf16;
}
/* If the last character won't fit, this won't split it */
size_t utf8size = utf8encode(ucc, utf8char) - utf8char;
if ((length += utf8size) < bufsize)
buffer = mempcpy(buffer, utf8char, utf8size);
utf16++;
}
/* Above won't ever copy to very end */
if (bufsize)
*buffer = '\0';
return length;
}
#define _toutf16(utf8) \
({ const char *_utf8 = (utf8); \
size_t _l = utf16len_utf8(_utf8); \
void *_buffer = alloca((_l + 1)*2); \
strcpy_utf8utf16(_buffer, _utf8); })
#define _toutf8(utf16) \
({ const char *_ucs = (utf16); \
size_t _l = strlen_utf16utf8(_ucs); \
void *_buffer = alloca(_l + 1); \
strcpy_utf16utf8(_buffer, _ucs); })
int os_open(const char *ospath, int oflag, ...)
{
return _wopen(_toutf16(ospath), oflag __OPEN_MODE_ARG);
}
int os_creat(const char *ospath, mode_t mode)
{
return _wcreat(_toutf16(ospath), mode);
}
int os_stat(const char *ospath, struct _stat *s)
{
return _wstat(_toutf16(ospath), s);
}
int os_remove(const char *ospath)
{
return _wremove(_toutf16(ospath));
}
int os_rename(const char *osold, const char *osnew)
{
int errnum = errno;
const wchar_t *wchosold = _toutf16(osold);
const wchar_t *wchosnew = _toutf16(osnew);
int rc = _wrename(wchosold, wchosnew);
if (rc < 0 && errno == EEXIST)
{
/* That didn't work; do cheap POSIX mimic */
BY_HANDLE_FILE_INFORMATION info;
if (win32_stat(osold, &info))
return -1;
if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!RemoveDirectoryW(wchosnew))
{
win32_last_error_errno();
return -1;
}
if (MoveFileExW(wchosold, wchosnew, MOVEFILE_REPLACE_EXISTING |
MOVEFILE_WRITE_THROUGH))
{
errno = errnum;
return 0;
}
errno = EIO;
}
return rc;
}
bool os_file_exists(const char *ospath)
{
HANDLE h = win32_open(ospath);
if (h == INVALID_HANDLE_VALUE)
return false;
CloseHandle(h);
return true;
}
_WDIR * os_opendir(const char *osdirname)
{
return _wopendir(_toutf16(osdirname));
}
int os_mkdir(const char *ospath, mode_t mode)
{
return _wmkdir(_toutf16(ospath));
(void)mode;
}
int os_rmdir(const char *ospath)
{
return _wrmdir(_toutf16(ospath));
}
int os_dirfd(_WDIR *osdirp)
{
#ifdef ENOTSUP
errno = ENOTSUP;
#else
errno = ENOSYS;
#endif
return -1;
(void)osdirp;
}
int os_opendirfd(const char *osdirname)
{
HANDLE h = win32_open(osdirname);
if (h == INVALID_HANDLE_VALUE)
return -1;
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(h, &info))
errno = EIO;
else if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
errno = ENOTDIR;
else
{
/* Convert OS handle to fd; the fd now owns it */
int osfd = _open_osfhandle((intptr_t)h, O_RDONLY);
if (osfd >= 0)
return osfd;
}
CloseHandle(h);
return -2;
}
#endif /* __MINGW32__ */
static size_t win32_path_strip_root(const char *ospath)
{
const char *p = ospath;
int c = toupper(*p);
if (c >= 'A' && c <= 'Z')
{
/* drive */
if ((c = *++p) == ':')
return 2;
}
if (c == '\\' && *++p == '\\')
{
/* UNC */
while ((c = *++p) && c != '/' && c != '\\');
return p - ospath;
}
return 0;
}
static HANDLE win32_open(const char *ospath)
{
/* FILE_FLAG_BACKUP_SEMANTICS is required for this to succeed at opening
a directory */
HANDLE h = CreateFileW(_toutf16(ospath), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE |
FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (h == INVALID_HANDLE_VALUE)
win32_last_error_errno();
return h;
}
static int win32_fstat(int osfd, HANDLE hFile,
LPBY_HANDLE_FILE_INFORMATION lpInfo)
{
/* The file descriptor takes precedence over the win32 file handle */
if (osfd >= 0)
hFile = (HANDLE)_get_osfhandle(osfd);
int rc = GetFileInformationByHandle(hFile, lpInfo) ? 0 : -1;
if (rc < 0)
win32_last_error_errno();
return rc;
}
static int win32_stat(const char *ospath, LPBY_HANDLE_FILE_INFORMATION lpInfo)
{
HANDLE h = win32_open(ospath);
if (h == INVALID_HANDLE_VALUE)
return -1;
int rc = win32_fstat(-1, h, lpInfo);
CloseHandle(h);
return rc;
}
int os_opendir_and_fd(const char *osdirname, _WDIR **osdirpp,
int *osfdp)
{
/* another possible way is to use open() then fdopendir() */
*osdirpp = NULL;
*osfdp = -1;
_WDIR *dirp = os_opendir(osdirname);
if (!dirp)
return -1;
int rc = 0;
int errnum = errno;
int fd = os_dirfd(dirp);
if (fd < 0)
{
fd = os_opendirfd(osdirname);
rc = 1;
}
if (fd < 0)
{
os_closedir(dirp);
return -2;
}
errno = errnum;
*osdirpp = dirp;
*osfdp = fd;
return rc;
}
int os_fsamefile(int osfd1, int osfd2)
{
BY_HANDLE_FILE_INFORMATION info1, info2;
if (!win32_fstat(osfd1, INVALID_HANDLE_VALUE, &info1) ||
!win32_fstat(osfd2, INVALID_HANDLE_VALUE, &info2))
return -1;
return SAME_FILE_INFO(&info1, &info2) ? 1 : 0;
}
int os_relate(const char *ospath1, const char *ospath2)
{
DEBUGF("\"%s\" : \"%s\"\n", ospath1, ospath2);
if (!ospath2 || !*ospath2)
{
errno = ospath2 ? ENOENT : EFAULT;
return -1;
}
/* First file must stay open for duration so that its stats don't change */
HANDLE h1 = win32_open(ospath1);
if (h1 == INVALID_HANDLE_VALUE)
return -2;
BY_HANDLE_FILE_INFORMATION info1;
if (win32_fstat(-1, h1, &info1))
{
CloseHandle(h1);
return -3;
}
char path2buf[strlen(ospath2) + 1];
*path2buf = 0;
ssize_t len = 0;
const char *p = ospath2;
size_t rootlen = win32_path_strip_root(ospath2);
const char *sepmo = PA_SEP_SOFT;
if (rootlen)
{
strmemcpy(path2buf, ospath2, rootlen);
ospath2 += rootlen;
sepmo = PA_SEP_HARD;
}
int rc = RELATE_DIFFERENT;
while (1)
{
if (sepmo != PA_SEP_HARD &&
!(len = parse_path_component(&ospath2, &p)))
{
break;
}
char compname[len + 1];
strmemcpy(compname, p, len);
path_append(path2buf, sepmo, compname, sizeof (path2buf));
sepmo = PA_SEP_SOFT;
int errnum = errno; /* save and restore if not actually failing */
BY_HANDLE_FILE_INFORMATION info2;
if (!win32_stat(path2buf, &info2))
{
if (SAME_FILE_INFO(&info1, &info2))
{
rc = RELATE_SAME;
}
else if (rc == RELATE_SAME)
{
if (name_is_dot_dot(compname))
rc = RELATE_DIFFERENT;
else if (!name_is_dot(compname))
rc = RELATE_PREFIX;
}
}
else if (errno == ENOENT && !*GOBBLE_PATH_SEPCH(ospath2) &&
!name_is_dot_dot(compname))
{
if (rc == RELATE_SAME)
rc = RELATE_PREFIX;
errno = errnum;
break;
}
else
{
rc = -4;
break;
}
}
CloseHandle(h1);
return rc;
}
int os_modtime(const char *path, time_t modtime)
{
(void)path;
(void)modtime;
return 0;
}
int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize);
void volume_size(IF_MV(int volume,) sector_t *sizep, sector_t *freep)
{
ULARGE_INTEGER free = { .QuadPart = 0 },
size = { .QuadPart = 0 };
char volpath[MAX_PATH];
if (os_volume_path(IF_MV(volume, ) volpath, sizeof (volpath)) >= 0)
GetDiskFreeSpaceExW(_toutf16(volpath), &free, &size, NULL);
if (sizep)
*sizep = size.QuadPart / 1024;
if (freep)
*freep = free.QuadPart / 1024;
}