@ -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 ~ 3 secs 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 ~ 3 secs 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 ( ) ;
@ -227,17 +261,149 @@ out:
return 0 ;
}
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 ) ;