1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/volume.cpp
3 // Purpose:     wxFSVolume - encapsulates system volume information
4 // Author:      George Policello
5 // Modified by:
6 // Created:     28 Jan 02
7 // Copyright:   (c) 2002 George Policello
8 // Licence:     wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 // ============================================================================
12 // declarations
13 // ============================================================================
14 
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18 
19 #include "wx/wxprec.h"
20 
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24 
25 #if wxUSE_FSVOLUME
26 
27 #include "wx/volume.h"
28 
29 #ifndef WX_PRECOMP
30     #if wxUSE_GUI
31         #include "wx/icon.h"
32     #endif
33     #include "wx/intl.h"
34     #include "wx/log.h"
35     #include "wx/hashmap.h"
36     #include "wx/filefn.h"
37 #endif // WX_PRECOMP
38 
39 #include "wx/dir.h"
40 #include "wx/dynlib.h"
41 #include "wx/arrimpl.cpp"
42 
43 // some compilers require including <windows.h> before <shellapi.h> so do it
44 // even if this is not necessary with most of them
45 #include "wx/msw/wrapwin.h"
46 #include <shellapi.h>
47 #include "wx/msw/wrapshl.h"
48 #include "wx/msw/missing.h"
49 
50 #if wxUSE_BASE
51 
52 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
53 // Dynamic library function defs.
54 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
55 
56 #if wxUSE_DYNLIB_CLASS
57 static wxDynamicLibrary s_mprLib;
58 #endif
59 
60 typedef DWORD (WINAPI* WNetOpenEnumPtr)(DWORD, DWORD, DWORD, LPNETRESOURCE, LPHANDLE);
61 typedef DWORD (WINAPI* WNetEnumResourcePtr)(HANDLE, LPDWORD, LPVOID, LPDWORD);
62 typedef DWORD (WINAPI* WNetCloseEnumPtr)(HANDLE);
63 
64 static WNetOpenEnumPtr s_pWNetOpenEnum;
65 static WNetEnumResourcePtr s_pWNetEnumResource;
66 static WNetCloseEnumPtr s_pWNetCloseEnum;
67 
68 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
69 // Globals/Statics
70 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
71 static long s_cancelSearch = FALSE;
72 
73 struct FileInfo
74 {
FileInfoFileInfo75     FileInfo(unsigned flag=0, wxFSVolumeKind type=wxFS_VOL_OTHER) :
76         m_flags(flag), m_type(type) {}
77 
FileInfoFileInfo78     FileInfo(const FileInfo& other) { *this = other; }
operator =FileInfo79     FileInfo& operator=(const FileInfo& other)
80     {
81         m_flags = other.m_flags;
82         m_type = other.m_type;
83         return *this;
84     }
85 
86     unsigned m_flags;
87     wxFSVolumeKind m_type;
88 };
89 WX_DECLARE_STRING_HASH_MAP(FileInfo, FileInfoMap);
90 // Cygwin bug (?) destructor for global s_fileInfo is called twice...
GetFileInfoMap()91 static FileInfoMap& GetFileInfoMap()
92 {
93     static FileInfoMap s_fileInfo(25);
94 
95     return s_fileInfo;
96 }
97 #define s_fileInfo (GetFileInfoMap())
98 
99 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
100 // Local helper functions.
101 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
102 
103 //=============================================================================
104 // Function: GetBasicFlags
105 // Purpose: Set basic flags, primarily wxFS_VOL_REMOTE and wxFS_VOL_REMOVABLE.
106 // Notes: - Local and mapped drives are mounted by definition.  We have no
107 //          way to determine mounted status of network drives, so assume that
108 //          all drives are mounted, and let the caller decide otherwise.
109 //        - Other flags are 'best guess' from type of drive.  The system will
110 //          not report the file attributes with any degree of accuracy.
111 //=============================================================================
GetBasicFlags(const wxChar * filename)112 static unsigned GetBasicFlags(const wxChar* filename)
113 {
114     unsigned flags = wxFS_VOL_MOUNTED;
115 
116     //----------------------------------
117     // 'Best Guess' based on drive type.
118     //----------------------------------
119     wxFSVolumeKind type;
120     switch(GetDriveType(filename))
121     {
122     case DRIVE_FIXED:
123         type = wxFS_VOL_DISK;
124         break;
125 
126     case DRIVE_REMOVABLE:
127         flags |= wxFS_VOL_REMOVABLE;
128         type = wxFS_VOL_FLOPPY;
129         break;
130 
131     case DRIVE_CDROM:
132         flags |= wxFS_VOL_REMOVABLE | wxFS_VOL_READONLY;
133         type = wxFS_VOL_CDROM;
134         break;
135 
136     case DRIVE_REMOTE:
137         flags |= wxFS_VOL_REMOTE;
138         type = wxFS_VOL_NETWORK;
139         break;
140 
141     case DRIVE_NO_ROOT_DIR:
142         flags &= ~wxFS_VOL_MOUNTED;
143         type = wxFS_VOL_OTHER;
144         break;
145 
146     default:
147         type = wxFS_VOL_OTHER;
148         break;
149     }
150 
151     //-----------------------------------------------------------------------
152     // The following most likely will not modify anything not set above,
153     // and will not work at all for network shares or empty CD ROM drives.
154     // But it is a good check if the Win API ever gets better about reporting
155     // this information.
156     //-----------------------------------------------------------------------
157     SHFILEINFO fi;
158     long rc = SHGetFileInfo(filename, 0, &fi, sizeof(fi), SHGFI_ATTRIBUTES);
159     if (!rc)
160     {
161         // this error is not fatal, so don't show a message to the user about
162         // it, otherwise it would appear every time a generic directory picker
163         // dialog is used and there is a connected network drive
164         wxLogLastError(wxT("SHGetFileInfo"));
165     }
166     else
167     {
168         if (fi.dwAttributes & SFGAO_READONLY)
169             flags |= wxFS_VOL_READONLY;
170         if (fi.dwAttributes & SFGAO_REMOVABLE)
171             flags |= wxFS_VOL_REMOVABLE;
172     }
173 
174     //------------------
175     // Flags are cached.
176     //------------------
177     s_fileInfo[filename] = FileInfo(flags, type);
178 
179     return flags;
180 } // GetBasicFlags
181 
182 //=============================================================================
183 // Function: FilteredAdd
184 // Purpose: Add a file to the list if it meets the filter requirement.
185 // Notes: - See GetBasicFlags for remarks about the Mounted flag.
186 //=============================================================================
FilteredAdd(wxArrayString & list,const wxChar * filename,unsigned flagsSet,unsigned flagsUnset)187 static bool FilteredAdd(wxArrayString& list, const wxChar* filename,
188                         unsigned flagsSet, unsigned flagsUnset)
189 {
190     bool accept = true;
191     unsigned flags = GetBasicFlags(filename);
192 
193     if (flagsSet & wxFS_VOL_MOUNTED && !(flags & wxFS_VOL_MOUNTED))
194         accept = false;
195     else if (flagsUnset & wxFS_VOL_MOUNTED && (flags & wxFS_VOL_MOUNTED))
196         accept = false;
197     else if (flagsSet & wxFS_VOL_REMOVABLE && !(flags & wxFS_VOL_REMOVABLE))
198         accept = false;
199     else if (flagsUnset & wxFS_VOL_REMOVABLE && (flags & wxFS_VOL_REMOVABLE))
200         accept = false;
201     else if (flagsSet & wxFS_VOL_READONLY && !(flags & wxFS_VOL_READONLY))
202         accept = false;
203     else if (flagsUnset & wxFS_VOL_READONLY && (flags & wxFS_VOL_READONLY))
204         accept = false;
205     else if (flagsSet & wxFS_VOL_REMOTE && !(flags & wxFS_VOL_REMOTE))
206         accept = false;
207     else if (flagsUnset & wxFS_VOL_REMOTE && (flags & wxFS_VOL_REMOTE))
208         accept = false;
209 
210     // Add to the list if passed the filter.
211     if (accept)
212         list.Add(filename);
213 
214     return accept;
215 } // FilteredAdd
216 
217 //=============================================================================
218 // Function: BuildListFromNN
219 // Purpose: Append or remove items from the list
220 // Notes: - There is no way to find all disconnected NN items, or even to find
221 //          all items while determining which are connected and not.  So this
222 //          function will find either all items or connected items.
223 //=============================================================================
BuildListFromNN(wxArrayString & list,NETRESOURCE * pResSrc,unsigned flagsSet,unsigned flagsUnset)224 static void BuildListFromNN(wxArrayString& list, NETRESOURCE* pResSrc,
225                             unsigned flagsSet, unsigned flagsUnset)
226 {
227     HANDLE hEnum;
228     int rc;
229 
230     //-----------------------------------------------
231     // Scope may be all drives or all mounted drives.
232     //-----------------------------------------------
233     unsigned scope = RESOURCE_GLOBALNET;
234     if (flagsSet & wxFS_VOL_MOUNTED)
235         scope = RESOURCE_CONNECTED;
236 
237     //----------------------------------------------------------------------
238     // Enumerate all items, adding only non-containers (ie. network shares).
239     // Containers cause a recursive call to this function for their own
240     // enumeration.
241     //----------------------------------------------------------------------
242     if (rc = s_pWNetOpenEnum(scope, RESOURCETYPE_DISK, 0, pResSrc, &hEnum), rc == NO_ERROR)
243     {
244         DWORD count = 1;
245         DWORD size = 256;
246         NETRESOURCE* pRes = (NETRESOURCE*)malloc(size);
247         memset(pRes, 0, sizeof(NETRESOURCE));
248         while (rc = s_pWNetEnumResource(hEnum, &count, pRes, &size), rc == NO_ERROR || rc == ERROR_MORE_DATA)
249         {
250             if (s_cancelSearch)
251                 break;
252 
253             if (rc == ERROR_MORE_DATA)
254             {
255                 pRes = (NETRESOURCE*)realloc(pRes, size);
256                 count = 1;
257             }
258             else if (count == 1)
259             {
260                 // Enumerate the container.
261                 if (pRes->dwUsage & RESOURCEUSAGE_CONTAINER)
262                 {
263                     BuildListFromNN(list, pRes, flagsSet, flagsUnset);
264                 }
265 
266                 // Add the network share.
267                 else
268                 {
269                     wxString filename(pRes->lpRemoteName);
270 
271                     // if the drive is unavailable, FilteredAdd() can hang for
272                     // a long time and, moreover, its failure appears to be not
273                     // cached so this will happen every time we use it, so try
274                     // a much quicker wxDirExists() test (which still hangs but
275                     // for much shorter time) for locally mapped drives first
276                     // to try to avoid this
277                     if ( pRes->lpLocalName &&
278                             *pRes->lpLocalName &&
279                                 !wxDirExists(pRes->lpLocalName) )
280                         continue;
281 
282                     if (!filename.empty())
283                     {
284                         if (filename.Last() != '\\')
285                             filename.Append('\\');
286 
287                         // The filter function will not know mounted from unmounted, and neither do we unless
288                         // we are iterating using RESOURCE_CONNECTED, in which case they all are mounted.
289                         // Volumes on disconnected servers, however, will correctly show as unmounted.
290                         FilteredAdd(list, filename.t_str(), flagsSet, flagsUnset&~wxFS_VOL_MOUNTED);
291                         if (scope == RESOURCE_GLOBALNET)
292                             s_fileInfo[filename].m_flags &= ~wxFS_VOL_MOUNTED;
293                     }
294                 }
295             }
296             else if (count == 0)
297                 break;
298         }
299         free(pRes);
300         s_pWNetCloseEnum(hEnum);
301     }
302 } // BuildListFromNN
303 
304 //=============================================================================
305 // Function: CompareFcn
306 // Purpose: Used to sort the NN list alphabetically, case insensitive.
307 //=============================================================================
CompareFcn(const wxString & first,const wxString & second)308 static int CompareFcn(const wxString& first, const wxString& second)
309 {
310     return wxStricmp(first.c_str(), second.c_str());
311 } // CompareFcn
312 
313 //=============================================================================
314 // Function: BuildRemoteList
315 // Purpose: Append Network Neighborhood items to the list.
316 // Notes: - Mounted gets transalated into Connected.  FilteredAdd is told
317 //          to ignore the Mounted flag since we need to handle it in a weird
318 //          way manually.
319 //        - The resulting list is sorted alphabetically.
320 //=============================================================================
BuildRemoteList(wxArrayString & list,NETRESOURCE * pResSrc,unsigned flagsSet,unsigned flagsUnset)321 static bool BuildRemoteList(wxArrayString& list, NETRESOURCE* pResSrc,
322                             unsigned flagsSet, unsigned flagsUnset)
323 {
324     // NN query depends on dynamically loaded library.
325     if (!s_pWNetOpenEnum || !s_pWNetEnumResource || !s_pWNetCloseEnum)
326     {
327         wxLogError(_("Failed to load mpr.dll."));
328         return false;
329     }
330 
331     // Don't waste time doing the work if the flags conflict.
332     if (flagsSet & wxFS_VOL_MOUNTED && flagsUnset & wxFS_VOL_MOUNTED)
333         return false;
334 
335     //----------------------------------------------
336     // Generate the list according to the flags set.
337     //----------------------------------------------
338     BuildListFromNN(list, pResSrc, flagsSet, flagsUnset);
339     list.Sort(CompareFcn);
340 
341     //-------------------------------------------------------------------------
342     // If mounted only is requested, then we only need one simple pass.
343     // Otherwise, we need to build a list of all NN volumes and then apply the
344     // list of mounted drives to it.
345     //-------------------------------------------------------------------------
346     if (!(flagsSet & wxFS_VOL_MOUNTED))
347     {
348         // generate.
349         wxArrayString mounted;
350         BuildListFromNN(mounted, pResSrc, flagsSet | wxFS_VOL_MOUNTED, flagsUnset & ~wxFS_VOL_MOUNTED);
351         mounted.Sort(CompareFcn);
352 
353         // apply list from bottom to top to preserve indexes if removing items.
354         ssize_t iList = list.GetCount()-1;
355         for (ssize_t iMounted = mounted.GetCount()-1; iMounted >= 0 && iList >= 0; iMounted--)
356         {
357             int compare;
358             wxString all(list[iList]);
359             wxString mount(mounted[iMounted]);
360 
361             while (compare =
362                      wxStricmp(list[iList].c_str(), mounted[iMounted].c_str()),
363                    compare > 0 && iList >= 0)
364             {
365                 iList--;
366                 all = list[iList];
367             }
368 
369 
370             if (compare == 0)
371             {
372                 // Found the element.  Remove it or mark it mounted.
373                 if (flagsUnset & wxFS_VOL_MOUNTED)
374                     list.RemoveAt(iList);
375                 else
376                     s_fileInfo[list[iList]].m_flags |= wxFS_VOL_MOUNTED;
377 
378             }
379 
380             iList--;
381         }
382     }
383 
384     return true;
385 } // BuildRemoteList
386 
387 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
388 // wxFSVolume
389 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
390 
391 //=============================================================================
392 // Function: GetVolumes
393 // Purpose: Generate and return a list of all volumes (drives) available.
394 // Notes:
395 //=============================================================================
GetVolumes(int flagsSet,int flagsUnset)396 wxArrayString wxFSVolumeBase::GetVolumes(int flagsSet, int flagsUnset)
397 {
398     ::InterlockedExchange(&s_cancelSearch, FALSE);     // reset
399 
400 #if wxUSE_DYNLIB_CLASS
401     if (!s_mprLib.IsLoaded() && s_mprLib.Load(wxT("mpr.dll")))
402     {
403 #ifdef UNICODE
404         s_pWNetOpenEnum = (WNetOpenEnumPtr)s_mprLib.GetSymbol(wxT("WNetOpenEnumW"));
405         s_pWNetEnumResource = (WNetEnumResourcePtr)s_mprLib.GetSymbol(wxT("WNetEnumResourceW"));
406 #else
407         s_pWNetOpenEnum = (WNetOpenEnumPtr)s_mprLib.GetSymbol(wxT("WNetOpenEnumA"));
408         s_pWNetEnumResource = (WNetEnumResourcePtr)s_mprLib.GetSymbol(wxT("WNetEnumResourceA"));
409 #endif
410         s_pWNetCloseEnum = (WNetCloseEnumPtr)s_mprLib.GetSymbol(wxT("WNetCloseEnum"));
411     }
412 #endif
413 
414     wxArrayString list;
415 
416     //-------------------------------
417     // Local and mapped drives first.
418     //-------------------------------
419     // Allocate the required space for the API call.
420     const DWORD chars = GetLogicalDriveStrings(0, NULL);
421     TCHAR* buf = new TCHAR[chars+1];
422 
423     // Get the list of drives.
424     GetLogicalDriveStrings(chars, buf);
425 
426     // Parse the list into an array, applying appropriate filters.
427     TCHAR *pVol;
428     pVol = buf;
429     while (*pVol)
430     {
431         FilteredAdd(list, pVol, flagsSet, flagsUnset);
432         pVol = pVol + wxStrlen(pVol) + 1;
433     }
434 
435     // Cleanup.
436     delete[] buf;
437 
438     //---------------------------
439     // Network Neighborhood next.
440     //---------------------------
441 
442     // not exclude remote and not removable
443     if (!(flagsUnset & wxFS_VOL_REMOTE) &&
444         !(flagsSet & wxFS_VOL_REMOVABLE)
445        )
446     {
447         // The returned list will be sorted alphabetically.  We don't pass
448         // our in since we don't want to change to order of the local drives.
449         wxArrayString nn;
450         if (BuildRemoteList(nn, 0, flagsSet, flagsUnset))
451         {
452             for (size_t idx = 0; idx < nn.GetCount(); idx++)
453                 list.Add(nn[idx]);
454         }
455     }
456 
457     return list;
458 } // GetVolumes
459 
460 //=============================================================================
461 // Function: CancelSearch
462 // Purpose: Instruct an active search to stop.
463 // Notes: - This will only sensibly be called by a thread other than the one
464 //          performing the search.  This is the only thread-safe function
465 //          provided by the class.
466 //=============================================================================
CancelSearch()467 void wxFSVolumeBase::CancelSearch()
468 {
469     ::InterlockedExchange(&s_cancelSearch, TRUE);
470 } // CancelSearch
471 
472 //=============================================================================
473 // Function: constructor
474 // Purpose: default constructor
475 //=============================================================================
wxFSVolumeBase()476 wxFSVolumeBase::wxFSVolumeBase()
477 {
478     m_isOk = false;
479 } // wxVolume
480 
481 //=============================================================================
482 // Function: constructor
483 // Purpose: constructor that calls Create
484 //=============================================================================
wxFSVolumeBase(const wxString & name)485 wxFSVolumeBase::wxFSVolumeBase(const wxString& name)
486 {
487     Create(name);
488 } // wxVolume
489 
490 //=============================================================================
491 // Function: Create
492 // Purpose: Finds, logs in, etc. to the request volume.
493 //=============================================================================
Create(const wxString & name)494 bool wxFSVolumeBase::Create(const wxString& name)
495 {
496     // assume fail.
497     m_isOk = false;
498 
499     // supplied.
500     m_volName = name;
501 
502     // Display name.
503     SHFILEINFO fi;
504     long rc = SHGetFileInfo(m_volName.t_str(), 0, &fi, sizeof(fi), SHGFI_DISPLAYNAME);
505     if (!rc)
506     {
507         wxLogError(_("Cannot read typename from '%s'!"), m_volName.c_str());
508         return false;
509     }
510     m_dispName = fi.szDisplayName;
511 
512     // all tests passed.
513     m_isOk = true;
514     return true;
515 } // Create
516 
517 //=============================================================================
518 // Function: IsOk
519 // Purpose: returns true if the volume is legal.
520 // Notes: For fixed disks, it must exist.  For removable disks, it must also
521 //        be present.  For Network Shares, it must also be logged in, etc.
522 //=============================================================================
IsOk() const523 bool wxFSVolumeBase::IsOk() const
524 {
525     return m_isOk;
526 } // IsOk
527 
528 //=============================================================================
529 // Function: GetKind
530 // Purpose: Return the type of the volume.
531 //=============================================================================
GetKind() const532 wxFSVolumeKind wxFSVolumeBase::GetKind() const
533 {
534     if (!m_isOk)
535         return wxFS_VOL_OTHER;
536 
537     FileInfoMap::iterator itr = s_fileInfo.find(m_volName);
538     if (itr == s_fileInfo.end())
539         return wxFS_VOL_OTHER;
540 
541     return itr->second.m_type;
542 }
543 
544 //=============================================================================
545 // Function: GetFlags
546 // Purpose: Return the caches flags for this volume.
547 // Notes: - Returns -1 if no flags were cached.
548 //=============================================================================
GetFlags() const549 int wxFSVolumeBase::GetFlags() const
550 {
551     if (!m_isOk)
552         return -1;
553 
554     FileInfoMap::iterator itr = s_fileInfo.find(m_volName);
555     if (itr == s_fileInfo.end())
556         return -1;
557 
558     return itr->second.m_flags;
559 } // GetFlags
560 
561 #endif // wxUSE_BASE
562 
563 // ============================================================================
564 // wxFSVolume
565 // ============================================================================
566 
567 #if wxUSE_GUI
568 
InitIcons()569 void wxFSVolume::InitIcons()
570 {
571     m_icons.Alloc(wxFS_VOL_ICO_MAX);
572     wxIcon null;
573     for (int idx = 0; idx < wxFS_VOL_ICO_MAX; idx++)
574         m_icons.Add(null);
575 }
576 
577 //=============================================================================
578 // Function: GetIcon
579 // Purpose: return the requested icon.
580 //=============================================================================
581 
GetIcon(wxFSIconType type) const582 wxIcon wxFSVolume::GetIcon(wxFSIconType type) const
583 {
584     wxCHECK_MSG( type >= 0 && (size_t)type < m_icons.GetCount(), wxNullIcon,
585                  wxT("wxFSIconType::GetIcon(): invalid icon index") );
586 
587 #ifdef __WXMSW__
588     // Load on demand.
589     if (m_icons[type].IsNull())
590     {
591         UINT flags = SHGFI_ICON;
592         switch (type)
593         {
594         case wxFS_VOL_ICO_SMALL:
595             flags |= SHGFI_SMALLICON;
596             break;
597 
598         case wxFS_VOL_ICO_LARGE:
599             flags |= SHGFI_SHELLICONSIZE;
600             break;
601 
602         case wxFS_VOL_ICO_SEL_SMALL:
603             flags |= SHGFI_SMALLICON | SHGFI_OPENICON;
604             break;
605 
606         case wxFS_VOL_ICO_SEL_LARGE:
607             flags |= SHGFI_SHELLICONSIZE | SHGFI_OPENICON;
608             break;
609 
610         case wxFS_VOL_ICO_MAX:
611             wxFAIL_MSG(wxT("wxFS_VOL_ICO_MAX is not valid icon type"));
612             break;
613         }
614 
615         SHFILEINFO fi;
616         long rc = SHGetFileInfo(m_volName.t_str(), 0, &fi, sizeof(fi), flags);
617         if (!rc || !fi.hIcon)
618         {
619             wxLogError(_("Cannot load icon from '%s'."), m_volName.c_str());
620         }
621         else
622         {
623             m_icons[type].CreateFromHICON((WXHICON)fi.hIcon);
624         }
625     }
626 
627     return m_icons[type];
628 #else
629     wxFAIL_MSG(wxS("Can't convert HICON to wxIcon in this port."));
630     return wxNullIcon;
631 #endif
632 } // GetIcon
633 
634 #endif // wxUSE_GUI
635 
636 #endif // wxUSE_FSVOLUME
637