aboutsummaryrefslogtreecommitdiff
path: root/winsup/cygwin/fhandler/netdrive.cc
diff options
context:
space:
mode:
Diffstat (limited to 'winsup/cygwin/fhandler/netdrive.cc')
-rw-r--r--winsup/cygwin/fhandler/netdrive.cc304
1 files changed, 233 insertions, 71 deletions
diff --git a/winsup/cygwin/fhandler/netdrive.cc b/winsup/cygwin/fhandler/netdrive.cc
index 1eff816..5328429 100644
--- a/winsup/cygwin/fhandler/netdrive.cc
+++ b/winsup/cygwin/fhandler/netdrive.cc
@@ -14,6 +14,7 @@ details. */
#include "dtable.h"
#include "cygheap.h"
#include "cygthread.h"
+#include "tls_pbuf.h"
#define USE_SYS_TYPES_FD_SET
#include <shobjidl.h>
@@ -28,8 +29,18 @@ details. */
/* SMBv1 is deprectated and not even installed by default anymore on
Windows 10 and 11 machines or their servers.
- So this fhandler class now uses Network Discovery, which, unfortunately,
- requires to use the shell API. */
+ As a result, neither WNetOpenEnumW() nor NetServerEnum() work as
+ expected anymore.
+
+ So this fhandler class now uses Network Discovery to enumerate
+ the "//" directory, which, unfortunately, requires to use the
+ shell API. */
+
+/* There's something REALLY fishy going on in Windows. If the NFS
+ enumeration via WNet functions is called *before* the share enumeration
+ via Shell function, the Shell functions will enumerate the NFS shares
+ instead of the SMB shares. Un-be-lie-va-ble!
+ FWIW, we reverted the SMB share enumeration using WNet. */
/* Define the required GUIDs here to avoid linking with libuuid.a */
const GUID FOLDERID_NetworkFolder = {
@@ -60,7 +71,8 @@ public:
free (entry[--num_entries]);
free (entry);
}
- void add (wchar_t *str)
+ size_t count () const { return num_entries; }
+ void add (wchar_t *str, bool downcase = false)
{
if (num_entries >= max_entries)
{
@@ -76,9 +88,9 @@ public:
entry[num_entries] = wcsdup (str);
if (entry[num_entries])
{
- wchar_t *p = entry[num_entries];
- while ((*p = towlower (*p)))
- ++p;
+ if (downcase)
+ for (wchar_t *p = entry[num_entries]; (*p = towlower (*p)); ++p)
+ ;
++num_entries;
}
}
@@ -96,6 +108,7 @@ struct netdriveinf
{
DIR *dir;
int err;
+ DWORD provider;
HANDLE sem;
};
@@ -110,8 +123,55 @@ hresult_to_errno (HRESULT wres)
return EACCES;
}
+/* Workaround incompatible definitions. */
+#define u_long __ms_u_long
+#define WS_FIONBIO _IOW('f', 126, u_long)
+#define WS_POLLOUT 0x10
+
+static bool
+server_is_running_nfs (const wchar_t *servername)
+{
+ /* Hack alarm: Only test TCP port 2049 */
+ struct addrinfoW hints = { 0 };
+ struct addrinfoW *ai = NULL, *aip;
+ bool ret = false;
+ INT wres;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ /* The services contains "nfs" only as UDP service... sigh. */
+ wres = GetAddrInfoW (servername, L"2049", &hints, &ai);
+ if (wres)
+ return false;
+ for (aip = ai; !ret && aip; aip = aip->ai_next)
+ {
+ SOCKET sock = ::socket (aip->ai_family, aip->ai_socktype,
+ aip->ai_flags);
+ if (sock != INVALID_SOCKET)
+ {
+ __ms_u_long nonblocking = 1;
+ ::ioctlsocket (sock, WS_FIONBIO, &nonblocking);
+ wres = ::connect (sock, aip->ai_addr, aip->ai_addrlen);
+ if (wres == 0)
+ ret = true;
+ else if (WSAGetLastError () == WSAEWOULDBLOCK)
+ {
+ WSAPOLLFD fds = { .fd = sock,
+ .events = WS_POLLOUT };
+ wres = WSAPoll (&fds, 1, 1500L);
+ if (wres > 0 && fds.revents == WS_POLLOUT)
+ ret = true;
+ }
+ ::closesocket (sock);
+ }
+ }
+ FreeAddrInfoW (ai);
+ return ret;
+}
+
+/* Use only to enumerate the Network top level. */
static DWORD
-thread_netdrive (void *arg)
+thread_netdrive_wsd (void *arg)
{
netdriveinf *ndi = (netdriveinf *) arg;
DIR *dir = ndi->dir;
@@ -121,7 +181,6 @@ thread_netdrive (void *arg)
ReleaseSemaphore (ndi->sem, 1, NULL);
- size_t len = strlen (dir->__d_dirname);
wres = CoInitialize (NULL);
if (FAILED (wres))
{
@@ -129,36 +188,15 @@ thread_netdrive (void *arg)
goto out;
}
- if (len == 2) /* // */
- {
- wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT,
- NULL, IID_PPV_ARGS (&netparent));
- if (FAILED (wres))
- {
- ndi->err = hresult_to_errno (wres);
- goto out;
- }
-
- }
- else
+ wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT,
+ NULL, IID_PPV_ARGS (&netparent));
+ if (FAILED (wres))
{
- wchar_t name[CYG_MAX_PATH];
-
- sys_mbstowcs (name, CYG_MAX_PATH, dir->__d_dirname);
- name[0] = L'\\';
- name[1] = L'\\';
- wres = SHCreateItemFromParsingName (name, NULL,
- IID_PPV_ARGS (&netparent));
- if (FAILED (wres))
- {
- ndi->err = hresult_to_errno (wres);
- goto out;
- }
-
+ ndi->err = hresult_to_errno (wres);
+ goto out;
}
- wres = netparent->BindToHandler (NULL, len == 2 ? BHID_StorageEnum
- : BHID_EnumItems,
+ wres = netparent->BindToHandler (NULL, BHID_StorageEnum,
IID_PPV_ARGS (&netitem_enum));
if (FAILED (wres))
{
@@ -167,31 +205,26 @@ thread_netdrive (void *arg)
goto out;
}
- if (len == 2)
- {
- netitem_enum->Reset ();
- /* Don't look at me!
-
- Network discovery is very unreliable and the list of machines
- returned is just fly-by-night, if the enumerator doesn't have
- enough time. The fact that you see *most* (but not necessarily
- *all*) machines on the network in Windows Explorer is a result of
- the enumeration running in a loop. You can observe this when
- rebooting a remote machine and it disappears and reappears in the
- Explorer Network list.
-
- However, this is no option for the command line. We need to be able
- to enumerate in a single go, since we can't just linger during
- readdir() and reset the enumeration multiple times until we have a
- supposedly full list.
-
- This makes the following Sleep necessary. Sleeping ~3secs after
- Reset fills the enumeration with high probability with almost all
- available machines. */
- Sleep (3000L);
- }
+ netitem_enum->Reset ();
+ /* Don't look at me!
- dir->__handle = (char *) new dir_cache ();
+ Network discovery is very unreliable and the list of machines
+ returned is just fly-by-night, if the enumerator doesn't have
+ enough time. The fact that you see *most* (but not necessarily
+ *all*) machines on the network in Windows Explorer is a result of
+ the enumeration running in a loop. You can observe this when
+ rebooting a remote machine and it disappears and reappears in the
+ Explorer Network list.
+
+ However, this is no option for the command line. We need to be able
+ to enumerate in a single go, since we can't just linger during
+ readdir() and reset the enumeration multiple times until we have a
+ supposedly full list.
+
+ This makes the following Sleep necessary. Sleeping ~3secs after
+ Reset fills the enumeration with high probability with almost all
+ available machines. */
+ Sleep (3000L);
do
{
@@ -207,7 +240,8 @@ thread_netdrive (void *arg)
if (netitem[idx]->GetDisplayName (SIGDN_PARENTRELATIVEPARSING,
&item_name) == S_OK)
{
- DIR_cache.add (item_name);
+ /* Skip "\\" on server names and downcase */
+ DIR_cache.add (item_name + 2, true);
CoTaskMemFree (item_name);
}
netitem[idx]->Release ();
@@ -228,16 +262,148 @@ out:
}
static DWORD
+thread_netdrive_wnet (void *arg)
+{
+ netdriveinf *ndi = (netdriveinf *) arg;
+ DIR *dir = ndi->dir;
+ DWORD wres;
+
+ size_t entry_cache_size = DIR_cache.count ();
+ WCHAR provider[256], *dummy = NULL;
+ wchar_t srv_name[CYG_MAX_PATH];
+ NETRESOURCEW nri = { 0 };
+ LPNETRESOURCEW nro;
+ tmp_pathbuf tp;
+ HANDLE dom = NULL;
+ DWORD cnt, size;
+
+ ReleaseSemaphore (ndi->sem, 1, NULL);
+
+ wres = WNetGetProviderNameW (ndi->provider, provider, (size = 256, &size));
+ if (wres != NO_ERROR)
+ {
+ ndi->err = geterrno_from_win_error (wres);
+ goto out;
+ }
+
+ sys_mbstowcs (srv_name, CYG_MAX_PATH, dir->__d_dirname);
+ srv_name[0] = L'\\';
+ srv_name[1] = L'\\';
+
+ if (ndi->provider == WNNC_NET_MS_NFS
+ && !server_is_running_nfs (srv_name + 2))
+ {
+ ndi->err = ENOENT;
+ goto out;
+ }
+
+ nri.lpRemoteName = srv_name;
+ nri.lpProvider = provider;
+ nri.dwType = RESOURCETYPE_DISK;
+ nro = (LPNETRESOURCEW) tp.c_get ();
+ wres = WNetGetResourceInformationW (&nri, nro,
+ (size = NT_MAX_PATH, &size), &dummy);
+ if (wres != NO_ERROR)
+ {
+ ndi->err = geterrno_from_win_error (wres);
+ goto out;
+ }
+ wres = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
+ RESOURCEUSAGE_ALL, nro, &dom);
+ if (wres != NO_ERROR)
+ {
+ ndi->err = geterrno_from_win_error (wres);
+ goto out;
+ }
+
+ while ((wres = WNetEnumResourceW (dom, (cnt = 1, &cnt), nro,
+ (size = NT_MAX_PATH, &size))) == NO_ERROR)
+ {
+ /* Skip server name and trailing backslash */
+ wchar_t *name = nro->lpRemoteName + wcslen (srv_name) + 1;
+ size_t cache_idx;
+
+ if (ndi->provider == WNNC_NET_MS_NFS)
+ {
+ wchar_t *nm = name;
+ /* Convert from "ANSI embedded in widechar" to multibyte and convert
+ back to widechar. */
+ char mbname[wcslen (name) + 1];
+ char *mb = mbname;
+ while ((*mb++ = *nm++))
+ ;
+ sys_mbstowcs_alloc (&name, HEAP_NOTHEAP, mbname);
+ if (!name)
+ {
+ ndi->err = ENOMEM;
+ goto out;
+ }
+ /* NFS has deep links so convert embedded '\\' to '/' here */
+ for (wchar_t *bs = name; (bs = wcschr (bs, L'\\')); *bs++ = L'/')
+ ;
+ }
+ /* If we already collected shares, drop duplicates. */
+ for (cache_idx = 0; cache_idx < entry_cache_size; ++ cache_idx)
+ if (!wcscmp (name, DIR_cache[cache_idx])) // wcscasecmp?
+ break;
+ if (cache_idx >= entry_cache_size)
+ DIR_cache.add (name);
+ if (ndi->provider == WNNC_NET_MS_NFS)
+ free (name);
+ }
+out:
+ if (dom)
+ WNetCloseEnum (dom);
+ ReleaseSemaphore (ndi->sem, 1, NULL);
+ return 0;
+}
+
+static DWORD
create_thread_and_wait (DIR *dir)
{
- netdriveinf ndi = { dir, 0,
- CreateSemaphore (&sec_none_nih, 0, 2, NULL) };
+ netdriveinf ndi = { dir, 0, 0, NULL };
+ cygthread *thr;
+
+ /* For the Network root, fetch WSD info. */
+ if (strlen (dir->__d_dirname) == 2)
+ {
+ ndi.provider = WNNC_NET_LANMAN;
+ ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL);
+ thr = new cygthread (thread_netdrive_wsd, &ndi, "netdrive_wsd");
+ if (thr->detach (ndi.sem))
+ ndi.err = EINTR;
+ CloseHandle (ndi.sem);
+ goto out;
+ }
+
+ /* For shares, use WNet functions. */
- cygthread *thr = new cygthread (thread_netdrive, &ndi, "netdrive");
+ /* Try NFS first, if the name contains a dot (i. e., supposedly is a FQDN
+ as used in NFS server enumeration). */
+ if (strchr (dir->__d_dirname, '.'))
+ {
+ ndi.provider = WNNC_NET_MS_NFS;
+ ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL);
+ thr = new cygthread (thread_netdrive_wnet, &ndi, "netdrive_nfs");
+ if (thr->detach (ndi.sem))
+ ndi.err = EINTR;
+ CloseHandle (ndi.sem);
+
+ if (ndi.err == EINTR)
+ goto out;
+
+ }
+
+ /* Eventually, try SMB via WNet for share enumeration. */
+ ndi.provider = WNNC_NET_LANMAN;
+ ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL);
+ thr = new cygthread (thread_netdrive_wnet, &ndi, "netdrive_smb");
if (thr->detach (ndi.sem))
ndi.err = EINTR;
CloseHandle (ndi.sem);
- return ndi.err;
+
+out:
+ return DIR_cache.count() > 0 ? 0 : ndi.err;
}
virtual_ftype_t
@@ -292,6 +458,7 @@ fhandler_netdrive::opendir (int fd)
int ret;
dir = fhandler_virtual::opendir (fd);
+ dir->__handle = (char *) new dir_cache ();
if (dir && (ret = create_thread_and_wait (dir)))
{
free (dir->__d_dirname);
@@ -315,19 +482,14 @@ fhandler_netdrive::readdir (DIR *dir, dirent *de)
goto out;
}
+ sys_wcstombs (de->d_name, sizeof de->d_name, DIR_cache[dir->__d_position]);
if (strlen (dir->__d_dirname) == 2)
- {
- sys_wcstombs (de->d_name, sizeof de->d_name,
- DIR_cache[dir->__d_position] + 2);
- de->d_ino = hash_path_name (get_ino (), de->d_name);
- }
+ de->d_ino = hash_path_name (get_ino (), de->d_name);
else
{
char full[2 * CYG_MAX_PATH];
char *s;
- sys_wcstombs (de->d_name, sizeof de->d_name,
- DIR_cache[dir->__d_position]);
s = stpcpy (full, dir->__d_dirname);
*s++ = '/';
stpcpy (s, de->d_name);