Merge branch 'sk/mingw-uni-fix'
* sk/mingw-uni-fix: Win32: Unicode file name support (dirent) Win32: Unicode file name support (except dirent)
This commit is contained in:
commit
80e85754e0
198
compat/mingw.c
198
compat/mingw.c
@ -1,6 +1,7 @@
|
||||
#include "../git-compat-util.h"
|
||||
#include "win32.h"
|
||||
#include <conio.h>
|
||||
#include <wchar.h>
|
||||
#include "../strbuf.h"
|
||||
#include "../run-command.h"
|
||||
|
||||
@ -198,14 +199,16 @@ static int ask_yes_no_if_possible(const char *format, ...)
|
||||
}
|
||||
}
|
||||
|
||||
#undef unlink
|
||||
int mingw_unlink(const char *pathname)
|
||||
{
|
||||
int ret, tries = 0;
|
||||
wchar_t wpathname[MAX_PATH];
|
||||
if (xutftowcs_path(wpathname, pathname) < 0)
|
||||
return -1;
|
||||
|
||||
/* read-only files cannot be removed */
|
||||
chmod(pathname, 0666);
|
||||
while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
_wchmod(wpathname, 0666);
|
||||
while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
if (!is_file_in_use_error(GetLastError()))
|
||||
break;
|
||||
/*
|
||||
@ -221,45 +224,45 @@ int mingw_unlink(const char *pathname)
|
||||
while (ret == -1 && is_file_in_use_error(GetLastError()) &&
|
||||
ask_yes_no_if_possible("Unlink of file '%s' failed. "
|
||||
"Should I try again?", pathname))
|
||||
ret = unlink(pathname);
|
||||
ret = _wunlink(wpathname);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int is_dir_empty(const char *path)
|
||||
static int is_dir_empty(const wchar_t *wpath)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
WIN32_FIND_DATAA findbuf;
|
||||
WIN32_FIND_DATAW findbuf;
|
||||
HANDLE handle;
|
||||
|
||||
strbuf_addf(&buf, "%s\\*", path);
|
||||
handle = FindFirstFileA(buf.buf, &findbuf);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
strbuf_release(&buf);
|
||||
wchar_t wbuf[MAX_PATH + 2];
|
||||
wcscpy(wbuf, wpath);
|
||||
wcscat(wbuf, L"\\*");
|
||||
handle = FindFirstFileW(wbuf, &findbuf);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
return GetLastError() == ERROR_NO_MORE_FILES;
|
||||
}
|
||||
|
||||
while (!strcmp(findbuf.cFileName, ".") ||
|
||||
!strcmp(findbuf.cFileName, ".."))
|
||||
if (!FindNextFile(handle, &findbuf)) {
|
||||
strbuf_release(&buf);
|
||||
return GetLastError() == ERROR_NO_MORE_FILES;
|
||||
while (!wcscmp(findbuf.cFileName, L".") ||
|
||||
!wcscmp(findbuf.cFileName, L".."))
|
||||
if (!FindNextFileW(handle, &findbuf)) {
|
||||
DWORD err = GetLastError();
|
||||
FindClose(handle);
|
||||
return err == ERROR_NO_MORE_FILES;
|
||||
}
|
||||
FindClose(handle);
|
||||
strbuf_release(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef rmdir
|
||||
int mingw_rmdir(const char *pathname)
|
||||
{
|
||||
int ret, tries = 0;
|
||||
wchar_t wpathname[MAX_PATH];
|
||||
if (xutftowcs_path(wpathname, pathname) < 0)
|
||||
return -1;
|
||||
|
||||
while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
if (!is_file_in_use_error(GetLastError()))
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
if (errno != EACCES)
|
||||
break;
|
||||
if (!is_dir_empty(pathname)) {
|
||||
if (!is_dir_empty(wpathname)) {
|
||||
errno = ENOTEMPTY;
|
||||
break;
|
||||
}
|
||||
@ -276,16 +279,26 @@ int mingw_rmdir(const char *pathname)
|
||||
while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
|
||||
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
|
||||
"Should I try again?", pathname))
|
||||
ret = rmdir(pathname);
|
||||
ret = _wrmdir(wpathname);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mingw_mkdir(const char *path, int mode)
|
||||
{
|
||||
int ret;
|
||||
wchar_t wpath[MAX_PATH];
|
||||
if (xutftowcs_path(wpath, path) < 0)
|
||||
return -1;
|
||||
ret = _wmkdir(wpath);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#undef open
|
||||
int mingw_open (const char *filename, int oflags, ...)
|
||||
{
|
||||
va_list args;
|
||||
unsigned mode;
|
||||
int fd;
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
|
||||
va_start(args, oflags);
|
||||
mode = va_arg(args, int);
|
||||
@ -294,10 +307,12 @@ int mingw_open (const char *filename, int oflags, ...)
|
||||
if (filename && !strcmp(filename, "/dev/null"))
|
||||
filename = "nul";
|
||||
|
||||
fd = open(filename, oflags, mode);
|
||||
if (xutftowcs_path(wfilename, filename) < 0)
|
||||
return -1;
|
||||
fd = _wopen(wfilename, oflags, mode);
|
||||
|
||||
if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) {
|
||||
DWORD attrs = GetFileAttributes(filename);
|
||||
DWORD attrs = GetFileAttributesW(wfilename);
|
||||
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
|
||||
errno = EISDIR;
|
||||
}
|
||||
@ -332,17 +347,28 @@ int mingw_fgetc(FILE *stream)
|
||||
#undef fopen
|
||||
FILE *mingw_fopen (const char *filename, const char *otype)
|
||||
{
|
||||
FILE *file;
|
||||
wchar_t wfilename[MAX_PATH], wotype[4];
|
||||
if (filename && !strcmp(filename, "/dev/null"))
|
||||
filename = "nul";
|
||||
return fopen(filename, otype);
|
||||
if (xutftowcs_path(wfilename, filename) < 0 ||
|
||||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
|
||||
return NULL;
|
||||
file = _wfopen(wfilename, wotype);
|
||||
return file;
|
||||
}
|
||||
|
||||
#undef freopen
|
||||
FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
|
||||
{
|
||||
FILE *file;
|
||||
wchar_t wfilename[MAX_PATH], wotype[4];
|
||||
if (filename && !strcmp(filename, "/dev/null"))
|
||||
filename = "nul";
|
||||
return freopen(filename, otype, stream);
|
||||
if (xutftowcs_path(wfilename, filename) < 0 ||
|
||||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
|
||||
return NULL;
|
||||
file = _wfreopen(wfilename, wotype, stream);
|
||||
return file;
|
||||
}
|
||||
|
||||
#undef fflush
|
||||
@ -367,6 +393,31 @@ int mingw_fflush(FILE *stream)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mingw_access(const char *filename, int mode)
|
||||
{
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
if (xutftowcs_path(wfilename, filename) < 0)
|
||||
return -1;
|
||||
/* X_OK is not supported by the MSVCRT version */
|
||||
return _waccess(wfilename, mode & ~X_OK);
|
||||
}
|
||||
|
||||
int mingw_chdir(const char *dirname)
|
||||
{
|
||||
wchar_t wdirname[MAX_PATH];
|
||||
if (xutftowcs_path(wdirname, dirname) < 0)
|
||||
return -1;
|
||||
return _wchdir(wdirname);
|
||||
}
|
||||
|
||||
int mingw_chmod(const char *filename, int mode)
|
||||
{
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
if (xutftowcs_path(wfilename, filename) < 0)
|
||||
return -1;
|
||||
return _wchmod(wfilename, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
|
||||
* Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
|
||||
@ -392,10 +443,12 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
|
||||
*/
|
||||
static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
||||
{
|
||||
int err;
|
||||
WIN32_FILE_ATTRIBUTE_DATA fdata;
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
if (xutftowcs_path(wfilename, file_name) < 0)
|
||||
return -1;
|
||||
|
||||
if (!(err = get_file_attr(file_name, &fdata))) {
|
||||
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
|
||||
buf->st_ino = 0;
|
||||
buf->st_gid = 0;
|
||||
buf->st_uid = 0;
|
||||
@ -408,8 +461,8 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
||||
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
|
||||
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
WIN32_FIND_DATAA findbuf;
|
||||
HANDLE handle = FindFirstFileA(file_name, &findbuf);
|
||||
WIN32_FIND_DATAW findbuf;
|
||||
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
|
||||
(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
|
||||
@ -428,7 +481,23 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
errno = err;
|
||||
switch (GetLastError()) {
|
||||
case ERROR_ACCESS_DENIED:
|
||||
case ERROR_SHARING_VIOLATION:
|
||||
case ERROR_LOCK_VIOLATION:
|
||||
case ERROR_SHARING_BUFFER_EXCEEDED:
|
||||
errno = EACCES;
|
||||
break;
|
||||
case ERROR_BUFFER_OVERFLOW:
|
||||
errno = ENAMETOOLONG;
|
||||
break;
|
||||
case ERROR_NOT_ENOUGH_MEMORY:
|
||||
errno = ENOMEM;
|
||||
break;
|
||||
default:
|
||||
errno = ENOENT;
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -516,16 +585,20 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
|
||||
{
|
||||
FILETIME mft, aft;
|
||||
int fh, rc;
|
||||
DWORD attrs;
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
if (xutftowcs_path(wfilename, file_name) < 0)
|
||||
return -1;
|
||||
|
||||
/* must have write permission */
|
||||
DWORD attrs = GetFileAttributes(file_name);
|
||||
attrs = GetFileAttributesW(wfilename);
|
||||
if (attrs != INVALID_FILE_ATTRIBUTES &&
|
||||
(attrs & FILE_ATTRIBUTE_READONLY)) {
|
||||
/* ignore errors here; open() will report them */
|
||||
SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY);
|
||||
SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY);
|
||||
}
|
||||
|
||||
if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) {
|
||||
if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) {
|
||||
rc = -1;
|
||||
goto revert_attrs;
|
||||
}
|
||||
@ -548,7 +621,7 @@ revert_attrs:
|
||||
if (attrs != INVALID_FILE_ATTRIBUTES &&
|
||||
(attrs & FILE_ATTRIBUTE_READONLY)) {
|
||||
/* ignore errors again */
|
||||
SetFileAttributes(file_name, attrs);
|
||||
SetFileAttributesW(wfilename, attrs);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -559,6 +632,18 @@ unsigned int sleep (unsigned int seconds)
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *mingw_mktemp(char *template)
|
||||
{
|
||||
wchar_t wtemplate[MAX_PATH];
|
||||
if (xutftowcs_path(wtemplate, template) < 0)
|
||||
return NULL;
|
||||
if (!_wmktemp(wtemplate))
|
||||
return NULL;
|
||||
if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0)
|
||||
return NULL;
|
||||
return template;
|
||||
}
|
||||
|
||||
int mkstemp(char *template)
|
||||
{
|
||||
char *filename = mktemp(template);
|
||||
@ -617,17 +702,18 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
|
||||
return result;
|
||||
}
|
||||
|
||||
#undef getcwd
|
||||
char *mingw_getcwd(char *pointer, int len)
|
||||
{
|
||||
int i;
|
||||
char *ret = getcwd(pointer, len);
|
||||
if (!ret)
|
||||
return ret;
|
||||
wchar_t wpointer[MAX_PATH];
|
||||
if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
|
||||
return NULL;
|
||||
if (xwcstoutf(pointer, wpointer, len) < 0)
|
||||
return NULL;
|
||||
for (i = 0; pointer[i]; i++)
|
||||
if (pointer[i] == '\\')
|
||||
pointer[i] = '/';
|
||||
return ret;
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1485,33 +1571,36 @@ int mingw_rename(const char *pold, const char *pnew)
|
||||
{
|
||||
DWORD attrs, gle;
|
||||
int tries = 0;
|
||||
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
|
||||
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Try native rename() first to get errno right.
|
||||
* It is based on MoveFile(), which cannot overwrite existing files.
|
||||
*/
|
||||
if (!rename(pold, pnew))
|
||||
if (!_wrename(wpold, wpnew))
|
||||
return 0;
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
repeat:
|
||||
if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
|
||||
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
||||
return 0;
|
||||
/* TODO: translate more errors */
|
||||
gle = GetLastError();
|
||||
if (gle == ERROR_ACCESS_DENIED &&
|
||||
(attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) {
|
||||
(attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
|
||||
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
errno = EISDIR;
|
||||
return -1;
|
||||
}
|
||||
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
|
||||
SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
|
||||
if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
|
||||
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
|
||||
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
||||
return 0;
|
||||
gle = GetLastError();
|
||||
/* revert file attributes on failure */
|
||||
SetFileAttributes(pnew, attrs);
|
||||
SetFileAttributesW(wpnew, attrs);
|
||||
}
|
||||
}
|
||||
if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
|
||||
@ -1757,11 +1846,16 @@ void mingw_open_html(const char *unixpath)
|
||||
|
||||
int link(const char *oldpath, const char *newpath)
|
||||
{
|
||||
typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
|
||||
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
|
||||
static T create_hard_link = NULL;
|
||||
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
|
||||
if (xutftowcs_path(woldpath, oldpath) < 0 ||
|
||||
xutftowcs_path(wnewpath, newpath) < 0)
|
||||
return -1;
|
||||
|
||||
if (!create_hard_link) {
|
||||
create_hard_link = (T) GetProcAddress(
|
||||
GetModuleHandle("kernel32.dll"), "CreateHardLinkA");
|
||||
GetModuleHandle("kernel32.dll"), "CreateHardLinkW");
|
||||
if (!create_hard_link)
|
||||
create_hard_link = (T)-1;
|
||||
}
|
||||
@ -1769,7 +1863,7 @@ int link(const char *oldpath, const char *newpath)
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
if (!create_hard_link(newpath, oldpath, NULL)) {
|
||||
if (!create_hard_link(wnewpath, woldpath, NULL)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
@ -121,10 +121,7 @@ static inline int fcntl(int fd, int cmd, ...)
|
||||
* simple adaptors
|
||||
*/
|
||||
|
||||
static inline int mingw_mkdir(const char *path, int mode)
|
||||
{
|
||||
return mkdir(path);
|
||||
}
|
||||
int mingw_mkdir(const char *path, int mode);
|
||||
#define mkdir mingw_mkdir
|
||||
|
||||
#define WNOHANG 1
|
||||
@ -195,6 +192,19 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream);
|
||||
int mingw_fflush(FILE *stream);
|
||||
#define fflush mingw_fflush
|
||||
|
||||
int mingw_access(const char *filename, int mode);
|
||||
#undef access
|
||||
#define access mingw_access
|
||||
|
||||
int mingw_chdir(const char *dirname);
|
||||
#define chdir mingw_chdir
|
||||
|
||||
int mingw_chmod(const char *filename, int mode);
|
||||
#define chmod mingw_chmod
|
||||
|
||||
char *mingw_mktemp(char *template);
|
||||
#define mktemp mingw_mktemp
|
||||
|
||||
char *mingw_getcwd(char *pointer, int len);
|
||||
#define getcwd mingw_getcwd
|
||||
|
||||
|
@ -6,10 +6,10 @@ struct DIR {
|
||||
int dd_stat; /* 0-based index */
|
||||
};
|
||||
|
||||
static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAA *fdata)
|
||||
static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
|
||||
{
|
||||
/* copy file name from WIN32_FIND_DATA to dirent */
|
||||
memcpy(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
|
||||
/* convert UTF-16 name to UTF-8 */
|
||||
xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
|
||||
|
||||
/* Set file type, based on WIN32_FIND_DATA */
|
||||
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
@ -20,25 +20,15 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAA *fdata)
|
||||
|
||||
DIR *opendir(const char *name)
|
||||
{
|
||||
char pattern[MAX_PATH];
|
||||
WIN32_FIND_DATAA fdata;
|
||||
wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
|
||||
WIN32_FIND_DATAW fdata;
|
||||
HANDLE h;
|
||||
int len;
|
||||
DIR *dir;
|
||||
|
||||
/* check that name is not NULL */
|
||||
if (!name) {
|
||||
errno = EINVAL;
|
||||
/* convert name to UTF-16 and check length < MAX_PATH */
|
||||
if ((len = xutftowcs_path(pattern, name)) < 0)
|
||||
return NULL;
|
||||
}
|
||||
/* check that the pattern won't be too long for FindFirstFileA */
|
||||
len = strlen(name);
|
||||
if (len + 2 >= MAX_PATH) {
|
||||
errno = ENAMETOOLONG;
|
||||
return NULL;
|
||||
}
|
||||
/* copy name to temp buffer */
|
||||
memcpy(pattern, name, len + 1);
|
||||
|
||||
/* append optional '/' and wildcard '*' */
|
||||
if (len && !is_dir_sep(pattern[len - 1]))
|
||||
@ -47,7 +37,7 @@ DIR *opendir(const char *name)
|
||||
pattern[len] = 0;
|
||||
|
||||
/* open find handle */
|
||||
h = FindFirstFileA(pattern, &fdata);
|
||||
h = FindFirstFileW(pattern, &fdata);
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
DWORD err = GetLastError();
|
||||
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
|
||||
@ -72,8 +62,8 @@ struct dirent *readdir(DIR *dir)
|
||||
/* if first entry, dirent has already been set up by opendir */
|
||||
if (dir->dd_stat) {
|
||||
/* get next entry and convert from WIN32_FIND_DATA to dirent */
|
||||
WIN32_FIND_DATAA fdata;
|
||||
if (FindNextFileA(dir->dd_handle, &fdata)) {
|
||||
WIN32_FIND_DATAW fdata;
|
||||
if (FindNextFileW(dir->dd_handle, &fdata)) {
|
||||
finddata2dirent(&dir->dd_dir, &fdata);
|
||||
} else {
|
||||
DWORD lasterr = GetLastError();
|
||||
|
@ -10,7 +10,7 @@ typedef struct DIR DIR;
|
||||
|
||||
struct dirent {
|
||||
unsigned char d_type; /* file type to prevent lstat after readdir */
|
||||
char d_name[MAX_PATH]; /* file name */
|
||||
char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
|
||||
};
|
||||
|
||||
DIR *opendir(const char *dirname);
|
||||
|
Loading…
Reference in New Issue
Block a user