Cygwin: //: fetch only one item per loop
[newlib-cygwin.git] / winsup / cygwin / fhandler / netdrive.cc
blob90e5a22175d81beeb717ce2d5c78f5458933ada5
1 /* fhandler_netdrive.cc: fhandler for // and //MACHINE handling
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include "cygerrno.h"
11 #include "security.h"
12 #include "path.h"
13 #include "fhandler.h"
14 #include "dtable.h"
15 #include "cygheap.h"
16 #include "cygthread.h"
17 #include "tls_pbuf.h"
19 #define USE_SYS_TYPES_FD_SET
20 #include <shobjidl.h>
21 #include <shlobj.h>
22 #include <lm.h>
23 #include <ws2tcpip.h>
25 #include <stdlib.h>
26 #include <dirent.h>
27 #include <wctype.h>
29 /* SMBv1 is deprectated and not even installed by default anymore on
30 Windows 10 and 11 machines or their servers.
32 As a result, neither WNetOpenEnumW() nor NetServerEnum() work as
33 expected anymore.
35 So this fhandler class now uses Network Discovery to enumerate
36 the "//" directory, which, unfortunately, requires to use the
37 shell API. */
39 /* There's something REALLY fishy going on in Windows. If the NFS
40 enumeration via WNet functions is called *before* the share enumeration
41 via Shell function, the Shell functions will enumerate the NFS shares
42 instead of the SMB shares. Un-be-lie-va-ble!
43 FWIW, we reverted the SMB share enumeration using WNet. */
45 #ifndef WNNC_NET_9P
46 #define WNNC_NET_9P 0x00480000
47 #endif
48 #define TERMSRV_DIR "tsclient"
49 #define PLAN9_DIR "wsl$"
51 /* Define the required GUIDs here to avoid linking with libuuid.a */
52 const GUID FOLDERID_NetworkFolder = {
53 0xd20beec4, 0x5ca8, 0x4905,
54 { 0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53 }
57 const GUID BHID_StorageEnum = {
58 0x4621a4e3, 0xf0d6, 0x4773,
59 { 0x8a, 0x9c, 0x46, 0xe7, 0x7b, 0x17, 0x48, 0x40 }
62 const GUID BHID_EnumItems = {
63 0x94f60519, 0x2850, 0x4924,
64 { 0xaa, 0x5a, 0xd1, 0x5e, 0x84, 0x86, 0x80, 0x39 }
67 class dir_cache
69 size_t max_entries;
70 size_t num_entries;
71 wchar_t **entry;
72 public:
73 dir_cache () : max_entries (0), num_entries (0), entry (NULL) {}
74 ~dir_cache ()
76 while (num_entries > 0)
77 free (entry[--num_entries]);
78 free (entry);
80 size_t count () const { return num_entries; }
81 void add (const wchar_t *str, bool downcase = false)
83 if (num_entries >= max_entries)
85 wchar_t **newentry;
87 newentry = (wchar_t **) realloc (entry, (max_entries + 10)
88 * sizeof (wchar_t *));
89 if (!newentry)
90 return;
91 entry = newentry;
92 max_entries += 10;
94 entry[num_entries] = wcsdup (str);
95 if (entry[num_entries])
97 if (downcase)
98 for (wchar_t *p = entry[num_entries]; (*p = towlower (*p)); ++p)
100 ++num_entries;
103 inline wchar_t *operator [](size_t idx) const
105 if (idx < num_entries)
106 return entry[idx];
107 return NULL;
111 #define DIR_cache (*reinterpret_cast<dir_cache *> (dir->__handle))
113 struct netdriveinf
115 DIR *dir;
116 int err;
117 DWORD provider;
118 HANDLE sem;
121 static inline int
122 hresult_to_errno (HRESULT wres)
124 if (SUCCEEDED (wres))
125 return 0;
126 if (((ULONG) wres & 0xffff0000)
127 == (ULONG) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0))
128 return geterrno_from_win_error ((ULONG) wres & 0xffff);
129 return EACCES;
132 /* Workaround incompatible definitions. */
133 #define u_long __ms_u_long
134 #define WS_FIONBIO _IOW('f', 126, u_long)
135 #define WS_POLLOUT 0x10
137 static bool
138 server_is_running_nfs (const wchar_t *servername)
140 /* Hack alarm: Only test TCP port 2049 */
141 struct addrinfoW hints = { 0 };
142 struct addrinfoW *ai = NULL, *aip;
143 bool ret = false;
144 INT wres;
146 hints.ai_family = AF_UNSPEC;
147 hints.ai_socktype = SOCK_STREAM;
148 /* The services contains "nfs" only as UDP service... sigh. */
149 wres = GetAddrInfoW (servername, L"2049", &hints, &ai);
150 if (wres)
151 return false;
152 for (aip = ai; !ret && aip; aip = aip->ai_next)
154 SOCKET sock = ::socket (aip->ai_family, aip->ai_socktype,
155 aip->ai_flags);
156 if (sock != INVALID_SOCKET)
158 __ms_u_long nonblocking = 1;
159 ::ioctlsocket (sock, WS_FIONBIO, &nonblocking);
160 wres = ::connect (sock, aip->ai_addr, aip->ai_addrlen);
161 if (wres == 0)
162 ret = true;
163 else if (WSAGetLastError () == WSAEWOULDBLOCK)
165 WSAPOLLFD fds = { .fd = sock,
166 .events = WS_POLLOUT };
167 wres = WSAPoll (&fds, 1, 1500L);
168 if (wres > 0 && fds.revents == WS_POLLOUT)
169 ret = true;
171 ::closesocket (sock);
174 FreeAddrInfoW (ai);
175 return ret;
178 /* Use only to enumerate the Network top level. */
179 static DWORD
180 thread_netdrive_wsd (void *arg)
182 netdriveinf *ndi = (netdriveinf *) arg;
183 DIR *dir = ndi->dir;
184 IEnumShellItems *netitem_enum;
185 IShellItem *netparent;
186 HRESULT wres;
188 ReleaseSemaphore (ndi->sem, 1, NULL);
190 wres = CoInitialize (NULL);
191 if (FAILED (wres))
193 ndi->err = hresult_to_errno (wres);
194 goto out;
197 wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT,
198 NULL, IID_PPV_ARGS (&netparent));
199 if (FAILED (wres))
201 ndi->err = hresult_to_errno (wres);
202 goto out;
205 wres = netparent->BindToHandler (NULL, BHID_StorageEnum,
206 IID_PPV_ARGS (&netitem_enum));
207 if (FAILED (wres))
209 ndi->err = hresult_to_errno (wres);
210 netparent->Release ();
211 goto out;
214 netitem_enum->Reset ();
215 /* Don't look at me!
217 Network discovery is very unreliable and the list of machines
218 returned is just fly-by-night, if the enumerator doesn't have
219 enough time. The fact that you see *most* (but not necessarily
220 *all*) machines on the network in Windows Explorer is a result of
221 the enumeration running in a loop. You can observe this when
222 rebooting a remote machine and it disappears and reappears in the
223 Explorer Network list.
225 However, this is no option for the command line. We need to be able
226 to enumerate in a single go, since we can't just linger during
227 readdir() and reset the enumeration multiple times until we have a
228 supposedly full list.
230 This makes the following Sleep necessary. Sleeping ~3secs after
231 Reset fills the enumeration with high probability with almost all
232 available machines. */
233 Sleep (3000L);
237 IShellItem *netitem = NULL;
238 LPWSTR item_name = NULL;
240 wres = netitem_enum->Next (1, &netitem, NULL);
241 if (wres == S_OK)
243 if (netitem->GetDisplayName (SIGDN_PARENTRELATIVEPARSING,
244 &item_name) == S_OK)
246 /* Skip "\\" on server names and downcase */
247 DIR_cache.add (item_name + 2, true);
248 CoTaskMemFree (item_name);
250 netitem->Release ();
253 while (wres == S_OK);
255 netitem_enum->Release ();
256 netparent->Release ();
258 ndi->err = 0;
260 out:
261 CoUninitialize ();
262 ReleaseSemaphore (ndi->sem, 1, NULL);
263 return 0;
266 static DWORD
267 thread_netdrive_wnet (void *arg)
269 netdriveinf *ndi = (netdriveinf *) arg;
270 DIR *dir = ndi->dir;
271 DWORD wres;
273 size_t entry_cache_size = DIR_cache.count ();
274 WCHAR provider[256], *dummy = NULL;
275 wchar_t srv_name[CYG_MAX_PATH];
276 NETRESOURCEW nri = { 0 };
277 LPNETRESOURCEW nro;
278 tmp_pathbuf tp;
279 HANDLE dom = NULL;
280 DWORD cnt, size;
282 ReleaseSemaphore (ndi->sem, 1, NULL);
284 wres = WNetGetProviderNameW (ndi->provider, provider, (size = 256, &size));
285 if (wres != NO_ERROR)
287 ndi->err = geterrno_from_win_error (wres);
288 goto out;
291 sys_mbstowcs (srv_name, CYG_MAX_PATH, dir->__d_dirname);
292 srv_name[0] = L'\\';
293 srv_name[1] = L'\\';
295 if (ndi->provider == WNNC_NET_MS_NFS
296 && !server_is_running_nfs (srv_name + 2))
298 ndi->err = ENOENT;
299 goto out;
302 nri.lpRemoteName = srv_name;
303 nri.lpProvider = provider;
304 nri.dwType = RESOURCETYPE_DISK;
305 nro = (LPNETRESOURCEW) tp.c_get ();
306 wres = WNetGetResourceInformationW (&nri, nro,
307 (size = NT_MAX_PATH, &size), &dummy);
308 if (wres != NO_ERROR)
310 ndi->err = geterrno_from_win_error (wres);
311 goto out;
313 wres = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
314 RESOURCEUSAGE_ALL, nro, &dom);
315 if (wres != NO_ERROR)
317 ndi->err = geterrno_from_win_error (wres);
318 goto out;
321 while ((wres = WNetEnumResourceW (dom, (cnt = 1, &cnt), nro,
322 (size = NT_MAX_PATH, &size))) == NO_ERROR)
324 size_t cache_idx;
326 /* Skip server name and trailing backslash */
327 wchar_t *name = nro->lpRemoteName + 2;
328 name = wcschr (name, L'\\');
329 if (!name)
330 continue;
331 ++name;
333 if (ndi->provider == WNNC_NET_MS_NFS)
335 wchar_t *nm = name;
336 /* Convert from "ANSI embedded in widechar" to multibyte and convert
337 back to widechar. */
338 char mbname[wcslen (name) + 1];
339 char *mb = mbname;
340 while ((*mb++ = *nm++))
342 sys_mbstowcs_alloc (&name, HEAP_NOTHEAP, mbname);
343 if (!name)
345 ndi->err = ENOMEM;
346 goto out;
348 /* NFS has deep links so convert embedded '\\' to '/' here */
349 for (wchar_t *bs = name; (bs = wcschr (bs, L'\\')); *bs++ = L'/')
352 /* If we already collected shares, drop duplicates. */
353 for (cache_idx = 0; cache_idx < entry_cache_size; ++ cache_idx)
354 if (!wcscmp (name, DIR_cache[cache_idx])) // wcscasecmp?
355 break;
356 if (cache_idx >= entry_cache_size)
357 DIR_cache.add (name);
358 if (ndi->provider == WNNC_NET_MS_NFS)
359 free (name);
361 out:
362 if (dom)
363 WNetCloseEnum (dom);
364 ReleaseSemaphore (ndi->sem, 1, NULL);
365 return 0;
368 static DWORD
369 create_thread_and_wait (DIR *dir)
371 netdriveinf ndi = { dir, 0, 0, NULL };
372 cygthread *thr;
374 /* For the Network root, fetch WSD info. */
375 if (strlen (dir->__d_dirname) == 2)
377 WCHAR provider[256];
378 DWORD size;
380 ndi.provider = WNNC_NET_SMB;
381 ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL);
382 thr = new cygthread (thread_netdrive_wsd, &ndi, "netdrive_wsd");
383 if (thr->detach (ndi.sem))
384 ndi.err = EINTR;
385 CloseHandle (ndi.sem);
386 /* Add wsl$ if Plan 9 is installed */
387 if (WNetGetProviderNameW (WNNC_NET_9P, provider, (size = 256, &size))
388 == NO_ERROR)
389 DIR_cache.add (__CONCAT(L,PLAN9_DIR));
390 goto out;
393 /* For shares, use WNet functions. */
395 /* Try NFS first, if the name contains a dot (i. e., supposedly is a FQDN
396 as used in NFS server enumeration). */
397 if (strchr (dir->__d_dirname, '.'))
399 ndi.provider = WNNC_NET_MS_NFS;
400 ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL);
401 thr = new cygthread (thread_netdrive_wnet, &ndi, "netdrive_nfs");
402 if (thr->detach (ndi.sem))
403 ndi.err = EINTR;
404 CloseHandle (ndi.sem);
406 if (ndi.err == EINTR)
407 goto out;
411 /* Eventually, try TERMSRV/P9/SMB via WNet for share enumeration,
412 depending on "server" name. */
413 if (!strcmp (dir->__d_dirname + 2, TERMSRV_DIR))
414 ndi.provider = WNNC_NET_TERMSRV;
415 else if (!strcmp (dir->__d_dirname + 2, PLAN9_DIR))
416 ndi.provider = WNNC_NET_9P;
417 else
418 ndi.provider = WNNC_NET_SMB;
419 ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL);
420 thr = new cygthread (thread_netdrive_wnet, &ndi, "netdrive_smb");
421 if (thr->detach (ndi.sem))
422 ndi.err = EINTR;
423 CloseHandle (ndi.sem);
425 out:
426 return DIR_cache.count() > 0 ? 0 : ndi.err;
429 virtual_ftype_t
430 fhandler_netdrive::exists ()
432 if (strlen (get_name ()) == 2)
433 return virt_rootdir;
435 wchar_t name[MAX_PATH];
436 struct addrinfoW *ai;
437 INT ret;
438 DWORD protocol = 0;
440 /* Handle "tsclient" (Microsoft Terminal Services) and
441 "wsl$" (Plan 9 Network Provider) explicitely.
442 Both obviously don't resolve with GetAddrInfoW. */
443 if (!strcmp (get_name () + 2, TERMSRV_DIR))
444 protocol = WNNC_NET_TERMSRV;
445 else if (!strcmp (get_name () + 2, PLAN9_DIR))
446 protocol = WNNC_NET_9P;
447 if (protocol)
449 WCHAR provider[256];
450 DWORD size = 256;
452 if (WNetGetProviderNameW (protocol, provider, &size) == NO_ERROR)
453 return virt_directory;
454 return virt_none;
456 /* Hopefully we are allowed to assume an IP network with existing name
457 resolution these days. Therefore, just try to resolve the name
458 into IP addresses. This may take up to about 3 secs if the name
459 doesn't exist, or about 8 secs if DNS is unavailable. */
460 sys_mbstowcs (name, CYG_MAX_PATH, get_name ());
461 ret = GetAddrInfoW (name + 2, NULL, NULL, &ai);
462 if (!ret)
463 FreeAddrInfoW (ai);
465 return ret ? virt_none : virt_directory;
468 fhandler_netdrive::fhandler_netdrive ():
469 fhandler_virtual ()
474 fhandler_netdrive::fstat (struct stat *buf)
476 const char *path = get_name ();
477 debug_printf ("fstat (%s)", path);
479 fhandler_base::fstat (buf);
481 buf->st_mode = S_IFDIR | STD_RBITS | STD_XBITS;
482 buf->st_ino = get_ino ();
484 return 0;
487 DIR *
488 fhandler_netdrive::opendir (int fd)
490 DIR *dir;
491 int ret;
493 dir = fhandler_virtual::opendir (fd);
494 dir->__handle = (char *) new dir_cache ();
495 if (dir && (ret = create_thread_and_wait (dir)))
497 free (dir->__d_dirname);
498 free (dir->__d_dirent);
499 free (dir);
500 dir = NULL;
501 set_errno (ret);
502 syscall_printf ("%p = opendir (%s)", dir, get_name ());
504 return dir;
508 fhandler_netdrive::readdir (DIR *dir, dirent *de)
510 int ret;
512 if (!DIR_cache[dir->__d_position])
514 ret = ENMFILE;
515 goto out;
518 sys_wcstombs (de->d_name, sizeof de->d_name, DIR_cache[dir->__d_position]);
519 if (strlen (dir->__d_dirname) == 2)
520 de->d_ino = hash_path_name (get_ino (), de->d_name);
521 else
523 char full[2 * CYG_MAX_PATH];
524 char *s;
526 s = stpcpy (full, dir->__d_dirname);
527 *s++ = '/';
528 stpcpy (s, de->d_name);
529 de->d_ino = readdir_get_ino (full, false);
531 dir->__d_position++;
532 de->d_type = DT_DIR;
533 ret = 0;
535 out:
536 syscall_printf ("%d = readdir(%p, %p)", ret, dir, de);
537 return ret;
540 void
541 fhandler_netdrive::seekdir (DIR *dir, long pos)
543 ::rewinddir (dir);
544 if (pos < 0)
545 return;
546 while (dir->__d_position < pos)
547 if (readdir (dir, dir->__d_dirent))
548 break;
551 void
552 fhandler_netdrive::rewinddir (DIR *dir)
554 dir->__d_position = 0;
558 fhandler_netdrive::closedir (DIR *dir)
560 if (dir->__handle != INVALID_HANDLE_VALUE)
561 delete &DIR_cache;
562 return fhandler_virtual::closedir (dir);
566 fhandler_netdrive::open (int flags, mode_t mode)
568 if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
570 set_errno (EEXIST);
571 return 0;
573 if (flags & O_WRONLY)
575 set_errno (EISDIR);
576 return 0;
578 /* Open a fake handle to \\Device\\Null */
579 return open_null (flags);
583 fhandler_netdrive::close ()
585 /* Skip fhandler_virtual::close, which is a no-op. */
586 return fhandler_base::close ();