4
0
mirror of git://sourceware.org/git/newlib-cygwin.git synced 2025-03-01 04:25:27 +08:00
Corinna Vinschen 8d2c1b4ce6 Cygwin: //server: check existence of server with getaddrinfo
Checking server existence by trying to enumerate its shares
may result in 2 minutes delay until some internal timeout is hit.

In the light that every network is an IP network anyway these
days, let's try with a simple getaddrinfo() call.  This is usually
back in 3 secs even if the server doesn't exist, and it's usually
back in 8 secs if the DNS server can't be connected.  This is the
fastest method I found to check server existence yet.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
2024-03-22 15:34:29 +01:00

393 lines
8.5 KiB
C++

/* fhandler_netdrive.cc: fhandler for // and //MACHINE handling
This file is part of Cygwin.
This software is a copyrighted work licensed under the terms of the
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */
#include "winsup.h"
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "cygthread.h"
#define USE_SYS_TYPES_FD_SET
#include <shobjidl.h>
#include <shlobj.h>
#include <lm.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <dirent.h>
#include <wctype.h>
/* 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. */
/* Define the required GUIDs here to avoid linking with libuuid.a */
const GUID FOLDERID_NetworkFolder = {
0xd20beec4, 0x5ca8, 0x4905,
{ 0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53 }
};
const GUID BHID_StorageEnum = {
0x4621a4e3, 0xf0d6, 0x4773,
{ 0x8a, 0x9c, 0x46, 0xe7, 0x7b, 0x17, 0x48, 0x40 }
};
const GUID BHID_EnumItems = {
0x94f60519, 0x2850, 0x4924,
{ 0xaa, 0x5a, 0xd1, 0x5e, 0x84, 0x86, 0x80, 0x39 }
};
class dir_cache
{
size_t max_entries;
size_t num_entries;
wchar_t **entry;
public:
dir_cache () : max_entries (0), num_entries (0), entry (NULL) {}
~dir_cache ()
{
while (num_entries > 0)
free (entry[--num_entries]);
free (entry);
}
void add (wchar_t *str)
{
if (num_entries >= max_entries)
{
wchar_t **newentry;
newentry = (wchar_t **) realloc (entry, (max_entries + 10)
* sizeof (wchar_t *));
if (!newentry)
return;
entry = newentry;
max_entries += 10;
}
entry[num_entries] = wcsdup (str);
if (entry[num_entries])
{
wchar_t *p = entry[num_entries];
while ((*p = towlower (*p)))
++p;
++num_entries;
}
}
inline wchar_t *operator [](size_t idx) const
{
if (idx < num_entries)
return entry[idx];
return NULL;
}
};
#define DIR_cache (*reinterpret_cast<dir_cache *> (dir->__handle))
struct netdriveinf
{
DIR *dir;
int err;
HANDLE sem;
};
static inline int
hresult_to_errno (HRESULT wres)
{
if (SUCCEEDED (wres))
return 0;
if (((ULONG) wres & 0xffff0000)
== (ULONG) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0))
return geterrno_from_win_error ((ULONG) wres & 0xffff);
return EACCES;
}
static DWORD
thread_netdrive (void *arg)
{
netdriveinf *ndi = (netdriveinf *) arg;
DIR *dir = ndi->dir;
IEnumShellItems *netitem_enum;
IShellItem *netparent;
HRESULT wres;
ReleaseSemaphore (ndi->sem, 1, NULL);
size_t len = strlen (dir->__d_dirname);
wres = CoInitialize (NULL);
if (FAILED (wres))
{
ndi->err = hresult_to_errno (wres);
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
{
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;
}
}
wres = netparent->BindToHandler (NULL, len == 2 ? BHID_StorageEnum
: BHID_EnumItems,
IID_PPV_ARGS (&netitem_enum));
if (FAILED (wres))
{
ndi->err = hresult_to_errno (wres);
netparent->Release ();
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);
}
dir->__handle = (char *) new dir_cache ();
do
{
IShellItem *netitem[10] = { 0 };
LPWSTR item_name = NULL;
ULONG count;
wres = netitem_enum->Next (10, netitem, &count);
if (SUCCEEDED (wres) && count > 0)
{
for (ULONG idx = 0; idx < count; ++idx)
{
if (netitem[idx]->GetDisplayName (SIGDN_PARENTRELATIVEPARSING,
&item_name) == S_OK)
{
DIR_cache.add (item_name);
CoTaskMemFree (item_name);
}
netitem[idx]->Release ();
}
}
}
while (wres == S_OK);
netitem_enum->Release ();
netparent->Release ();
ndi->err = 0;
out:
CoUninitialize ();
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) };
cygthread *thr = new cygthread (thread_netdrive, &ndi, "netdrive");
if (thr->detach (ndi.sem))
ndi.err = EINTR;
CloseHandle (ndi.sem);
return ndi.err;
}
virtual_ftype_t
fhandler_netdrive::exists ()
{
if (strlen (get_name ()) == 2)
return virt_rootdir;
wchar_t name[MAX_PATH];
struct addrinfoW *ai;
INT ret;
/* Hopefully we are allowed to assume an IP network with existing name
resolution these days. Therefore, just try to resolve the name
into IP addresses. This may take up to about 3 secs if the name
doesn't exist, or about 8 secs if DNS is unavailable.
Don't ask for "tsclient", it doesn't resolve. Just assume it exists. */
if (!strcmp (get_name () + 2, "tsclient"))
return virt_directory;
sys_mbstowcs (name, CYG_MAX_PATH, get_name ());
ret = GetAddrInfoW (name + 2, NULL, NULL, &ai);
if (!ret)
FreeAddrInfoW (ai);
return ret ? virt_none : virt_directory;
}
fhandler_netdrive::fhandler_netdrive ():
fhandler_virtual ()
{
}
int
fhandler_netdrive::fstat (struct stat *buf)
{
const char *path = get_name ();
debug_printf ("fstat (%s)", path);
fhandler_base::fstat (buf);
buf->st_mode = S_IFDIR | STD_RBITS | STD_XBITS;
buf->st_ino = get_ino ();
return 0;
}
DIR *
fhandler_netdrive::opendir (int fd)
{
DIR *dir;
int ret;
dir = fhandler_virtual::opendir (fd);
if (dir && (ret = create_thread_and_wait (dir)))
{
free (dir->__d_dirname);
free (dir->__d_dirent);
free (dir);
dir = NULL;
set_errno (ret);
syscall_printf ("%p = opendir (%s)", dir, get_name ());
}
return dir;
}
int
fhandler_netdrive::readdir (DIR *dir, dirent *de)
{
int ret;
if (!DIR_cache[dir->__d_position])
{
ret = ENMFILE;
goto out;
}
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);
}
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);
de->d_ino = readdir_get_ino (full, false);
}
dir->__d_position++;
de->d_type = DT_DIR;
ret = 0;
out:
syscall_printf ("%d = readdir(%p, %p)", ret, dir, de);
return ret;
}
void
fhandler_netdrive::seekdir (DIR *dir, long pos)
{
::rewinddir (dir);
if (pos < 0)
return;
while (dir->__d_position < pos)
if (readdir (dir, dir->__d_dirent))
break;
}
void
fhandler_netdrive::rewinddir (DIR *dir)
{
dir->__d_position = 0;
}
int
fhandler_netdrive::closedir (DIR *dir)
{
if (dir->__handle != INVALID_HANDLE_VALUE)
delete &DIR_cache;
return fhandler_virtual::closedir (dir);
}
int
fhandler_netdrive::open (int flags, mode_t mode)
{
if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
{
set_errno (EEXIST);
return 0;
}
if (flags & O_WRONLY)
{
set_errno (EISDIR);
return 0;
}
/* Open a fake handle to \\Device\\Null */
return open_null (flags);
}
int
fhandler_netdrive::close ()
{
/* Skip fhandler_virtual::close, which is a no-op. */
return fhandler_base::close ();
}