From 1d53705be54ede60d01e94d5f8f113a2660b118a Mon Sep 17 00:00:00 2001 From: Oleg S Date: Sat, 27 May 2017 01:54:34 +0300 Subject: [PATCH] win32: Fix enumerate root dir content on SFTP-server (#148) https://github.com/PowerShell/Win32-OpenSSH/issues/539 --- contrib/win32/win32compat/fileio.c | 11 +- contrib/win32/win32compat/misc.c | 16 ++- contrib/win32/win32compat/misc_internal.h | 7 ++ contrib/win32/win32compat/win32_dirent.c | 130 +++++++++++++++++++++- 4 files changed, 156 insertions(+), 8 deletions(-) diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c index 248910236..6245dba9c 100644 --- a/contrib/win32/win32compat/fileio.c +++ b/contrib/win32/win32compat/fileio.c @@ -745,12 +745,21 @@ fileio_stat(const char *path, struct _stat64 *buf) wchar_t* wpath = NULL; WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 }; int ret = -1, len = 0; + + memset(buf, 0, sizeof(struct _stat64)); + + /* Detect root dir */ + if (path && strcmp(path, "/") == 0) { + buf->st_mode = _S_IFDIR | _S_IREAD | 0xFF; + buf->st_dev = USHRT_MAX; // rootdir flag + return 0; + } + if ((wpath = utf8_to_utf16(path)) == NULL) { errno = errno_from_Win32LastError(); debug3("utf8_to_utf16 failed for file:%s error:%d", path, GetLastError()); return -1; } - memset(buf, 0, sizeof(struct _stat64)); if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &attributes) == FALSE) { errno = errno_from_Win32LastError(); diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 1836fcdb4..1a1639b3d 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -851,6 +851,16 @@ convertToBackslash(char *str) } } +void +convertToBackslashW(wchar_t *str) +{ + while (*str) { + if (*str == L'/') + *str = L'\\'; + str++; + } +} + /* convert back slash to forward slash */ void convertToForwardslash(char *str) @@ -874,9 +884,9 @@ realpath(const char *path, char resolved[PATH_MAX]) char tempPath[PATH_MAX]; if ((path[0] == '/') && path[1] && (path[2] == ':')) - strncpy(resolved, path + 1, strlen(path)); /* skip the first '/' */ + strncpy(resolved, path + 1, PATH_MAX); /* skip the first '/' */ else - strncpy(resolved, path, strlen(path) + 1); + strncpy(resolved, path, PATH_MAX); if ((resolved[0]) && (resolved[1] == ':') && (resolved[2] == '\0')) { /* make "x:" as "x:\\" */ resolved[2] = '\\'; @@ -889,7 +899,7 @@ realpath(const char *path, char resolved[PATH_MAX]) convertToForwardslash(tempPath); resolved[0] = '/'; /* will be our first slash in /x:/users/test1 format */ - strncpy(resolved + 1, tempPath, sizeof(tempPath) - 1); + strncpy(resolved + 1, tempPath, PATH_MAX - 1); return resolved; } diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index 0d7903be9..a33892488 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -4,6 +4,12 @@ #define SSH_ASYNC_STDOUT "SSH_ASYNC_STDOUT" #define SSH_ASYNC_STDERR "SSH_ASYNC_STDERR" +#define GOTO_CLEANUP_IF(_cond_,_err_) do { \ + if ((_cond_)) { \ + hr = _err_; \ + goto cleanup; \ + } \ +} while(0) #define NULL_DEVICE "/dev/null" #define IS_INVALID_HANDLE(h) ( ((NULL == h) || (INVALID_HANDLE_VALUE == h)) ? 1 : 0 ) @@ -17,6 +23,7 @@ void w32posix_done(); char* w32_programdir(); void convertToBackslash(char *str); +void convertToBackslashW(wchar_t *str); void convertToForwardslash(char *str); void unix_time_to_file_time(ULONG, LPFILETIME); diff --git a/contrib/win32/win32compat/win32_dirent.c b/contrib/win32/win32compat/win32_dirent.c index f9810d073..5fc286fa3 100644 --- a/contrib/win32/win32compat/win32_dirent.c +++ b/contrib/win32/win32compat/win32_dirent.c @@ -42,8 +42,54 @@ struct DIR_ { intptr_t hFile; struct _wfinddata_t c_file; int first; + wchar_t * nextdisk; }; +#define ATTR_ROOTDIR UINT_MAX + +/* Enumerate all devices which have drive name. + Return a DIR stream on the root directory, or NULL if it could not be enumerated. */ +DIR * +openrootdir(const char *name) +{ + int hr = 0; + DWORD dw; + DIR * pdir; + struct _wfinddata_t c_file = {0}; + wchar_t * p; + + dw = GetLogicalDriveStringsW(_countof(c_file.name) - 2, c_file.name); + if (!dw) { + errno = ENODEV; + return NULL; + } + c_file.attrib = ATTR_ROOTDIR; + c_file.size = 0; + p = c_file.name; + while (*p) { + size_t len = wcslen(p); + if (len == 0) + break; + p += len + 1; + c_file.size++; + } + if (c_file.size == 0) { + errno = ENODEV; + return NULL; + } + pdir = malloc(sizeof(DIR)); + if (!pdir) { + errno = ENOMEM; + return NULL; + } + memset(pdir, 0, sizeof(DIR)); + pdir->hFile = 0; + memcpy(&pdir->c_file, &c_file, sizeof(c_file)); + pdir->first = 1; + + return pdir; +} + /* Open a directory stream on NAME. Return a DIR stream on the directory, or NULL if it could not be opened. */ DIR * @@ -55,14 +101,31 @@ opendir(const char *name) wchar_t searchstr[PATH_MAX]; wchar_t* wname = NULL; int needed; + size_t len; + + /* Detect root dir */ + if (name && strcmp(name, "/") == 0) + return openrootdir(name); if ((wname = utf8_to_utf16(sanitized_path(name))) == NULL) { errno = ENOMEM; return NULL; } + convertToBackslashW(wname); + len = wcslen(wname); + if (len && wname[len-1] == L'\\') { + len--; + wname[len] = 0; + } + if (len >= PATH_MAX) { + free(wname); + errno = ENAMETOOLONG; + return NULL; + } + /* add *.* for Windows _findfirst() search pattern */ - swprintf_s(searchstr, PATH_MAX, L"%s\\*.*", wname); + swprintf_s(searchstr, _countof(searchstr) - 1, L"%s\\*.*", wname); free(wname); if ((hFile = _wfindfirst(searchstr, &c_file)) == -1L) @@ -92,13 +155,69 @@ closedir(DIR *dirp) if (dirp && (dirp->hFile)) { _findclose(dirp->hFile); - dirp->hFile = 0; - free(dirp); } + free(dirp); return 0; } +/* Read a root directory entry from DIRP. + Return a pointer to a `struct dirent' describing the entry, + or NULL for EOF or error. The storage returned may be overwritten + by a later readdir call on the same DIR stream. */ +struct dirent * +readrootdir(DIR * dirp) +{ + wchar_t * p; + size_t len = 0; + struct dirent *pdirentry; + UINT dt; + ULARGE_INTEGER totalNumberOfBytes; + BOOL x; + + if (dirp->c_file.size <= 0) { + errno = ENODATA; + return NULL; + } + if (dirp->first) { + dirp->first = 0; + dirp->nextdisk = dirp->c_file.name; + } + + p = dirp->nextdisk; + + for ( ; ; p += len + 1) { + len = wcslen(p); + if (len == 0) { + dirp->nextdisk = p; + errno = ENODATA; + return NULL; /* end of multi-string */ + } + + dt = GetDriveTypeW(p); + if (dt == DRIVE_UNKNOWN || dt == DRIVE_NO_ROOT_DIR || dt == DRIVE_RAMDISK) + continue; + + x = GetDiskFreeSpaceExW(p, NULL, &totalNumberOfBytes, NULL); + if (!x || totalNumberOfBytes.QuadPart == 0) + continue; + + break; // process filtered disk + } + dirp->nextdisk = p + len + 1; + + if ((pdirentry = malloc(sizeof(struct dirent))) == NULL) { + errno = ENOMEM; + return NULL; + } + pdirentry->d_name[0] = (char)p[0]; + pdirentry->d_name[1] = ':'; + pdirentry->d_name[2] = 0; + + pdirentry->d_ino = 1; // a fictious one like UNIX to say it is nonzero + return pdirentry; +} + /* Read a directory entry from DIRP. Return a pointer to a `struct dirent' describing the entry, or NULL for EOF or error. The storage returned may be overwritten @@ -113,6 +232,9 @@ readdir(void *avp) DIR *dirp = (DIR *)avp; char *tmp = NULL; + if (dirp->hFile == 0 && dirp->c_file.attrib == ATTR_ROOTDIR) + return readrootdir(dirp); + for (;;) { if (dirp->first) { memcpy(&c_file, &dirp->c_file, sizeof(c_file)); @@ -128,7 +250,7 @@ readdir(void *avp) return NULL; } - strncpy(pdirentry.d_name, tmp, strlen(tmp) + 1); + strncpy(pdirentry.d_name, tmp, sizeof(pdirentry.d_name)); free(tmp); pdirentry.d_ino = 1; /* a fictious one like UNIX to say it is nonzero */