msvcrt: Mark some fall-throughs in a switch statement.
[wine] / dlls / shell32 / shlfileop.c
1 /*
2  * SHFileOperation
3  *
4  * Copyright 2000 Juergen Schmied
5  * Copyright 2002 Andriy Palamarchuk
6  * Copyright 2004 Dietrich Teickner (from Odin)
7  * Copyright 2004 Rolf Kalbermatter
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23
24 #include "config.h"
25 #include "wine/port.h"
26
27 #include <stdarg.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <assert.h>
31
32 #include "windef.h"
33 #include "winbase.h"
34 #include "winreg.h"
35 #include "shellapi.h"
36 #include "wingdi.h"
37 #include "winuser.h"
38 #include "shlobj.h"
39 #include "shresdef.h"
40 #define NO_SHLWAPI_STREAM
41 #include "shlwapi.h"
42 #include "shell32_main.h"
43 #include "undocshell.h"
44 #include "wine/debug.h"
45 #include "xdg.h"
46
47 WINE_DEFAULT_DEBUG_CHANNEL(shell);
48
49 #define IsAttrib(x, y)  ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y)))
50 #define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY))
51 #define IsAttribDir(x)  IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY)
52 #define IsDotDir(x)     ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
53
54 #define FO_MASK         0xF
55
56 static const WCHAR wWildcardFile[] = {'*',0};
57 static const WCHAR wWildcardChars[] = {'*','?',0};
58
59 static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec);
60 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
61 static DWORD SHNotifyRemoveDirectoryA(LPCSTR path);
62 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
63 static DWORD SHNotifyDeleteFileA(LPCSTR path);
64 static DWORD SHNotifyDeleteFileW(LPCWSTR path);
65 static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest);
66 static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
67 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
68
69 typedef struct
70 {
71     SHFILEOPSTRUCTW *req;
72     DWORD dwYesToAllMask;
73     BOOL bManyItems;
74     BOOL bCancelled;
75 } FILE_OPERATION;
76
77 /* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations
78  */
79 static const WCHAR CONFIRM_MSG_PROP[] = {'W','I','N','E','_','C','O','N','F','I','R','M',0};
80
81 struct confirm_msg_info
82 {
83     LPWSTR lpszText;
84     LPWSTR lpszCaption;
85     HICON hIcon;
86     BOOL bYesToAll;
87 };
88
89 /* as some buttons may be hidden and the dialog height may change we may need
90  * to move the controls */
91 static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow)
92 {
93     HWND hButton = GetDlgItem(hDlg, iId);
94     RECT r;
95
96     if (bShow) {
97         int width;
98
99         GetWindowRect(hButton, &r);
100         MapWindowPoints( 0, hDlg, (POINT *)&r, 2 );
101         width = r.right - r.left;
102         SetWindowPos(hButton, 0, *xPos - width, r.top - yOffset, 0, 0,
103                      SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW );
104         *xPos -= width + 5;
105     }
106     else
107         ShowWindow(hButton, SW_HIDE);
108 }
109
110 /* Note: we paint the text manually and don't use the static control to make
111  * sure the text has the same height as the one computed in WM_INITDIALOG
112  */
113 static INT_PTR ConfirmMsgBox_Paint(HWND hDlg)
114 {
115     PAINTSTRUCT ps;
116     HFONT hOldFont;
117     RECT r;
118     HDC hdc;
119
120     BeginPaint(hDlg, &ps);
121     hdc = ps.hdc;
122
123     GetClientRect(GetDlgItem(hDlg, IDD_MESSAGE), &r);
124     /* this will remap the rect to dialog coords */
125     MapWindowPoints(GetDlgItem(hDlg, IDD_MESSAGE), hDlg, (LPPOINT)&r, 2);
126     hOldFont = SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDD_MESSAGE, WM_GETFONT, 0, 0));
127     DrawTextW(hdc, GetPropW(hDlg, CONFIRM_MSG_PROP), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK);
128     SelectObject(hdc, hOldFont);
129     EndPaint(hDlg, &ps);
130     return TRUE;
131 }
132
133 static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam)
134 {
135     struct confirm_msg_info *info = (struct confirm_msg_info *)lParam;
136     INT xPos, yOffset;
137     int width, height;
138     HFONT hOldFont;
139     HDC hdc;
140     RECT r;
141
142     SetWindowTextW(hDlg, info->lpszCaption);
143     ShowWindow(GetDlgItem(hDlg, IDD_MESSAGE), SW_HIDE);
144     SetPropW(hDlg, CONFIRM_MSG_PROP, info->lpszText);
145     SendDlgItemMessageW(hDlg, IDD_ICON, STM_SETICON, (WPARAM)info->hIcon, 0);
146
147     /* compute the text height and resize the dialog */
148     GetClientRect(GetDlgItem(hDlg, IDD_MESSAGE), &r);
149     hdc = GetDC(hDlg);
150     yOffset = r.bottom;
151     hOldFont = SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDD_MESSAGE, WM_GETFONT, 0, 0));
152     DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT);
153     SelectObject(hdc, hOldFont);
154     yOffset -= r.bottom;
155     yOffset = min(yOffset, 35);  /* don't make the dialog too small */
156     ReleaseDC(hDlg, hdc);
157
158     GetClientRect(hDlg, &r);
159     xPos = r.right - 7;
160     GetWindowRect(hDlg, &r);
161     width = r.right - r.left;
162     height = r.bottom - r.top - yOffset;
163     MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2,
164         (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE);
165
166     confirm_msg_move_button(hDlg, IDCANCEL,     &xPos, yOffset, info->bYesToAll);
167     confirm_msg_move_button(hDlg, IDNO,         &xPos, yOffset, TRUE);
168     confirm_msg_move_button(hDlg, IDD_YESTOALL, &xPos, yOffset, info->bYesToAll);
169     confirm_msg_move_button(hDlg, IDYES,        &xPos, yOffset, TRUE);
170     return TRUE;
171 }
172
173 static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
174 {
175     switch (uMsg)
176     {
177         case WM_INITDIALOG:
178             return ConfirmMsgBox_Init(hDlg, lParam);
179         case WM_PAINT:
180             return ConfirmMsgBox_Paint(hDlg);
181         case WM_COMMAND:
182             EndDialog(hDlg, wParam);
183             break;
184         case WM_CLOSE:
185             EndDialog(hDlg, IDCANCEL);
186             break;
187     }
188     return FALSE;
189 }
190
191 static int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll)
192 {
193     static const WCHAR wszTemplate[] = {'S','H','E','L','L','_','Y','E','S','T','O','A','L','L','_','M','S','G','B','O','X',0};
194     struct confirm_msg_info info;
195
196     info.lpszText = lpszText;
197     info.lpszCaption = lpszCaption;
198     info.hIcon = hIcon;
199     info.bYesToAll = bYesToAll;
200     return DialogBoxParamW(shell32_hInstance, wszTemplate, hWnd, ConfirmMsgBoxProc, (LPARAM)&info);
201 }
202
203 /* confirmation dialogs content */
204 typedef struct
205 {
206         HINSTANCE hIconInstance;
207         UINT icon_resource_id;
208         UINT caption_resource_id, text_resource_id;
209 } SHELL_ConfirmIDstruc;
210
211 static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids)
212 {
213         ids->hIconInstance = shell32_hInstance;
214         switch (nKindOfDialog) {
215           case ASK_DELETE_FILE:
216             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
217             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
218             ids->text_resource_id  = IDS_DELETEITEM_TEXT;
219             return TRUE;
220           case ASK_DELETE_FOLDER:
221             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
222             ids->caption_resource_id  = IDS_DELETEFOLDER_CAPTION;
223             ids->text_resource_id  = IDS_DELETEITEM_TEXT;
224             return TRUE;
225           case ASK_DELETE_MULTIPLE_ITEM:
226             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
227             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
228             ids->text_resource_id  = IDS_DELETEMULTIPLE_TEXT;
229             return TRUE;
230           case ASK_TRASH_FILE:
231             ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
232             ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
233             ids->text_resource_id = IDS_TRASHITEM_TEXT;
234             return TRUE;
235           case ASK_TRASH_FOLDER:
236             ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
237             ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
238             ids->text_resource_id = IDS_TRASHFOLDER_TEXT;
239             return TRUE;
240           case ASK_TRASH_MULTIPLE_ITEM:
241             ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
242             ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
243             ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT;
244             return TRUE;
245           case ASK_CANT_TRASH_ITEM:
246             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
247             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
248             ids->text_resource_id  = IDS_CANTTRASH_TEXT;
249             return TRUE;
250           case ASK_DELETE_SELECTED:
251             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
252             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
253             ids->text_resource_id  = IDS_DELETESELECTED_TEXT;
254             return TRUE;
255           case ASK_OVERWRITE_FILE:
256             ids->hIconInstance = NULL;
257             ids->icon_resource_id = IDI_WARNING;
258             ids->caption_resource_id  = IDS_OVERWRITEFILE_CAPTION;
259             ids->text_resource_id  = IDS_OVERWRITEFILE_TEXT;
260             return TRUE;
261           case ASK_OVERWRITE_FOLDER:
262             ids->hIconInstance = NULL;
263             ids->icon_resource_id = IDI_WARNING;
264             ids->caption_resource_id  = IDS_OVERWRITEFILE_CAPTION;
265             ids->text_resource_id  = IDS_OVERWRITEFOLDER_TEXT;
266             return TRUE;
267           default:
268             FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
269         }
270         return FALSE;
271 }
272
273 static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op)
274 {
275         WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
276         SHELL_ConfirmIDstruc ids;
277         DWORD_PTR args[1];
278         HICON hIcon;
279         int ret;
280
281         assert(nKindOfDialog >= 0 && nKindOfDialog < 32);
282         if (op && (op->dwYesToAllMask & (1 << nKindOfDialog)))
283             return TRUE;
284
285         if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE;
286
287         LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR));
288         LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR));
289
290         args[0] = (DWORD_PTR)szDir;
291         FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
292                        szText, 0, 0, szBuffer, sizeof(szBuffer), (__ms_va_list*)args);
293         hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id));
294
295         ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems);
296         if (op) {
297             if (ret == IDD_YESTOALL) {
298                 op->dwYesToAllMask |= (1 << nKindOfDialog);
299                 ret = IDYES;
300             }
301             if (ret == IDCANCEL)
302                 op->bCancelled = TRUE;
303             if (ret != IDYES)
304                 op->req->fAnyOperationsAborted = TRUE;
305         }
306         return ret == IDYES;
307 }
308
309 BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir)
310 {
311     return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL);
312 }
313
314 static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars)
315 {
316         DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0);
317
318         if (len < minChars)
319           len = minChars;
320
321         *wPath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
322         if (*wPath)
323         {
324           MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len);
325           return NO_ERROR;
326         }
327         return E_OUTOFMEMORY;
328 }
329
330 static void SHELL32_FreeUnicodeBuf(LPWSTR wPath)
331 {
332         HeapFree(GetProcessHeap(), 0, wPath);
333 }
334
335 HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status)
336 {
337     FIXME("(%s, %p) stub\n", debugstr_w(path), status);
338     return E_FAIL;
339 }
340
341 /**************************************************************************
342  * SHELL_DeleteDirectory()  [internal]
343  *
344  * Asks for confirmation when bShowUI is true and deletes the directory and
345  * all its subdirectories and files if necessary.
346  */
347 static BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI)
348 {
349         BOOL    ret = TRUE;
350         HANDLE  hFind;
351         WIN32_FIND_DATAW wfd;
352         WCHAR   szTemp[MAX_PATH];
353
354         /* Make sure the directory exists before eventually prompting the user */
355         PathCombineW(szTemp, pszDir, wWildcardFile);
356         hFind = FindFirstFileW(szTemp, &wfd);
357         if (hFind == INVALID_HANDLE_VALUE)
358           return FALSE;
359
360         if (!bShowUI || (ret = SHELL_ConfirmDialogW(hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
361         {
362           do
363           {
364             if (IsDotDir(wfd.cFileName))
365               continue;
366             PathCombineW(szTemp, pszDir, wfd.cFileName);
367             if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
368               ret = SHELL_DeleteDirectoryW(hwnd, szTemp, FALSE);
369             else
370               ret = (SHNotifyDeleteFileW(szTemp) == ERROR_SUCCESS);
371           } while (ret && FindNextFileW(hFind, &wfd));
372         }
373         FindClose(hFind);
374         if (ret)
375           ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS);
376         return ret;
377 }
378
379 /**************************************************************************
380  * Win32CreateDirectory      [SHELL32.93]
381  *
382  * Creates a directory. Also triggers a change notify if one exists.
383  *
384  * PARAMS
385  *  path       [I]   path to directory to create
386  *
387  * RETURNS
388  *  TRUE if successful, FALSE otherwise
389  *
390  * NOTES
391  *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
392  *  This is Unicode on NT/2000
393  */
394 static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec)
395 {
396         LPWSTR wPath;
397         DWORD retCode;
398
399         TRACE("(%s, %p)\n", debugstr_a(path), sec);
400
401         retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
402         if (!retCode)
403         {
404           retCode = SHNotifyCreateDirectoryW(wPath, sec);
405           SHELL32_FreeUnicodeBuf(wPath);
406         }
407         return retCode;
408 }
409
410 /**********************************************************************/
411
412 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
413 {
414         TRACE("(%s, %p)\n", debugstr_w(path), sec);
415
416         if (CreateDirectoryW(path, sec))
417         {
418           SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
419           return ERROR_SUCCESS;
420         }
421         return GetLastError();
422 }
423
424 /**********************************************************************/
425
426 BOOL WINAPI Win32CreateDirectoryAW(LPCVOID path, LPSECURITY_ATTRIBUTES sec)
427 {
428         if (SHELL_OsIsUnicode())
429           return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS);
430         return (SHNotifyCreateDirectoryA(path, sec) == ERROR_SUCCESS);
431 }
432
433 /************************************************************************
434  * Win32RemoveDirectory      [SHELL32.94]
435  *
436  * Deletes a directory. Also triggers a change notify if one exists.
437  *
438  * PARAMS
439  *  path       [I]   path to directory to delete
440  *
441  * RETURNS
442  *  TRUE if successful, FALSE otherwise
443  *
444  * NOTES
445  *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
446  *  This is Unicode on NT/2000
447  */
448 static DWORD SHNotifyRemoveDirectoryA(LPCSTR path)
449 {
450         LPWSTR wPath;
451         DWORD retCode;
452
453         TRACE("(%s)\n", debugstr_a(path));
454
455         retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
456         if (!retCode)
457         {
458           retCode = SHNotifyRemoveDirectoryW(wPath);
459           SHELL32_FreeUnicodeBuf(wPath);
460         }
461         return retCode;
462 }
463
464 /***********************************************************************/
465
466 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path)
467 {
468         BOOL ret;
469         TRACE("(%s)\n", debugstr_w(path));
470
471         ret = RemoveDirectoryW(path);
472         if (!ret)
473         {
474           /* Directory may be write protected */
475           DWORD dwAttr = GetFileAttributesW(path);
476           if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY))
477             if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY))
478               ret = RemoveDirectoryW(path);
479         }
480         if (ret)
481         {
482           SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL);
483           return ERROR_SUCCESS;
484         }
485         return GetLastError();
486 }
487
488 /***********************************************************************/
489
490 BOOL WINAPI Win32RemoveDirectoryAW(LPCVOID path)
491 {
492         if (SHELL_OsIsUnicode())
493           return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
494         return (SHNotifyRemoveDirectoryA(path) == ERROR_SUCCESS);
495 }
496
497 /************************************************************************
498  * Win32DeleteFile           [SHELL32.164]
499  *
500  * Deletes a file. Also triggers a change notify if one exists.
501  *
502  * PARAMS
503  *  path       [I]   path to file to delete
504  *
505  * RETURNS
506  *  TRUE if successful, FALSE otherwise
507  *
508  * NOTES
509  *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
510  *  This is Unicode on NT/2000
511  */
512 static DWORD SHNotifyDeleteFileA(LPCSTR path)
513 {
514         LPWSTR wPath;
515         DWORD retCode;
516
517         TRACE("(%s)\n", debugstr_a(path));
518
519         retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
520         if (!retCode)
521         {
522           retCode = SHNotifyDeleteFileW(wPath);
523           SHELL32_FreeUnicodeBuf(wPath);
524         }
525         return retCode;
526 }
527
528 /***********************************************************************/
529
530 static DWORD SHNotifyDeleteFileW(LPCWSTR path)
531 {
532         BOOL ret;
533
534         TRACE("(%s)\n", debugstr_w(path));
535
536         ret = DeleteFileW(path);
537         if (!ret)
538         {
539           /* File may be write protected or a system file */
540           DWORD dwAttr = GetFileAttributesW(path);
541           if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
542             if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
543               ret = DeleteFileW(path);
544         }
545         if (ret)
546         {
547           SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
548           return ERROR_SUCCESS;
549         }
550         return GetLastError();
551 }
552
553 /***********************************************************************/
554
555 DWORD WINAPI Win32DeleteFileAW(LPCVOID path)
556 {
557         if (SHELL_OsIsUnicode())
558           return (SHNotifyDeleteFileW(path) == ERROR_SUCCESS);
559         return (SHNotifyDeleteFileA(path) == ERROR_SUCCESS);
560 }
561
562 /************************************************************************
563  * SHNotifyMoveFile          [internal]
564  *
565  * Moves a file. Also triggers a change notify if one exists.
566  *
567  * PARAMS
568  *  src        [I]   path to source file to move
569  *  dest       [I]   path to target file to move to
570  *
571  * RETURNS
572  *  ERORR_SUCCESS if successful
573  */
574 static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest)
575 {
576         BOOL ret;
577
578         TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
579
580         ret = MoveFileExW(src, dest, MOVEFILE_REPLACE_EXISTING);
581
582         /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
583         if (!ret)
584             ret = MoveFileW(src, dest);
585
586         if (!ret)
587         {
588           DWORD dwAttr;
589
590           dwAttr = SHFindAttrW(dest, FALSE);
591           if (INVALID_FILE_ATTRIBUTES == dwAttr)
592           {
593             /* Source file may be write protected or a system file */
594             dwAttr = GetFileAttributesW(src);
595             if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
596               if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
597                 ret = MoveFileW(src, dest);
598           }
599         }
600         if (ret)
601         {
602           SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW, src, dest);
603           return ERROR_SUCCESS;
604         }
605         return GetLastError();
606 }
607
608 /************************************************************************
609  * SHNotifyCopyFile          [internal]
610  *
611  * Copies a file. Also triggers a change notify if one exists.
612  *
613  * PARAMS
614  *  src           [I]   path to source file to move
615  *  dest          [I]   path to target file to move to
616  *  bFailIfExists [I]   if TRUE, the target file will not be overwritten if
617  *                      a file with this name already exists
618  *
619  * RETURNS
620  *  ERROR_SUCCESS if successful
621  */
622 static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
623 {
624         BOOL ret;
625         DWORD attribs;
626
627         TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
628
629         /* Destination file may already exist with read only attribute */
630         attribs = GetFileAttributesW(dest);
631         if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY))
632           SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
633
634         ret = CopyFileW(src, dest, bFailIfExists);
635         if (ret)
636         {
637           SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
638           return ERROR_SUCCESS;
639         }
640
641         return GetLastError();
642 }
643
644 /*************************************************************************
645  * SHCreateDirectory         [SHELL32.165]
646  *
647  * This function creates a file system folder whose fully qualified path is
648  * given by path. If one or more of the intermediate folders do not exist,
649  * they will be created as well.
650  *
651  * PARAMS
652  *  hWnd       [I]
653  *  path       [I]   path of directory to create
654  *
655  * RETURNS
656  *  ERROR_SUCCESS or one of the following values:
657  *  ERROR_BAD_PATHNAME if the path is relative
658  *  ERROR_FILE_EXISTS when a file with that name exists
659  *  ERROR_PATH_NOT_FOUND can't find the path, probably invalid
660  *  ERROR_INVALID_NAME if the path contains invalid chars
661  *  ERROR_ALREADY_EXISTS when the directory already exists
662  *  ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
663  *
664  * NOTES
665  *  exported by ordinal
666  *  Win9x exports ANSI
667  *  WinNT/2000 exports Unicode
668  */
669 DWORD WINAPI SHCreateDirectory(HWND hWnd, LPCVOID path)
670 {
671         if (SHELL_OsIsUnicode())
672           return SHCreateDirectoryExW(hWnd, path, NULL);
673         return SHCreateDirectoryExA(hWnd, path, NULL);
674 }
675
676 /*************************************************************************
677  * SHCreateDirectoryExA      [SHELL32.@]
678  *
679  * This function creates a file system folder whose fully qualified path is
680  * given by path. If one or more of the intermediate folders do not exist,
681  * they will be created as well.
682  *
683  * PARAMS
684  *  hWnd       [I]
685  *  path       [I]   path of directory to create
686  *  sec        [I]   security attributes to use or NULL
687  *
688  * RETURNS
689  *  ERROR_SUCCESS or one of the following values:
690  *  ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative
691  *  ERROR_INVALID_NAME if the path contains invalid chars
692  *  ERROR_FILE_EXISTS when a file with that name exists
693  *  ERROR_ALREADY_EXISTS when the directory already exists
694  *  ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
695  *
696  *  FIXME: Not implemented yet;
697  *  SHCreateDirectoryEx also verifies that the files in the directory will be visible
698  *  if the path is a network path to deal with network drivers which might have a limited
699  *  but unknown maximum path length. If not:
700  *
701  *  If hWnd is set to a valid window handle, a message box is displayed warning
702  *  the user that the files may not be accessible. If the user chooses not to
703  *  proceed, the function returns ERROR_CANCELLED.
704  *
705  *  If hWnd is set to NULL, no user interface is displayed and the function
706  *  returns ERROR_CANCELLED.
707  */
708 int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
709 {
710         LPWSTR wPath;
711         DWORD retCode;
712
713         TRACE("(%s, %p)\n", debugstr_a(path), sec);
714
715         retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
716         if (!retCode)
717         {
718           retCode = SHCreateDirectoryExW(hWnd, wPath, sec);
719           SHELL32_FreeUnicodeBuf(wPath);
720         }
721         return retCode;
722 }
723
724 /*************************************************************************
725  * SHCreateDirectoryExW      [SHELL32.@]
726  *
727  * See SHCreateDirectoryExA.
728  */
729 int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
730 {
731         int ret = ERROR_BAD_PATHNAME;
732         TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec);
733
734         if (PathIsRelativeW(path))
735         {
736           SetLastError(ret);
737         }
738         else
739         {
740           ret = SHNotifyCreateDirectoryW(path, sec);
741           /* Refuse to work on certain error codes before trying to create directories recursively */
742           if (ret != ERROR_SUCCESS &&
743               ret != ERROR_FILE_EXISTS &&
744               ret != ERROR_ALREADY_EXISTS &&
745               ret != ERROR_FILENAME_EXCED_RANGE)
746           {
747             WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1];  /* extra for PathAddBackslash() */
748
749             lstrcpynW(szTemp, path, MAX_PATH);
750             pEnd = PathAddBackslashW(szTemp);
751             pSlash = szTemp + 3;
752
753             while (*pSlash)
754             {
755               while (*pSlash && *pSlash != '\\') pSlash++;
756               if (*pSlash)
757               {
758                 *pSlash = 0;    /* terminate path at separator */
759
760                 ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL);
761               }
762               *pSlash++ = '\\'; /* put the separator back */
763             }
764           }
765
766           if (ret && hWnd && (ERROR_CANCELLED != ret))
767           {
768             /* We failed and should show a dialog box */
769             FIXME("Show system error message, creating path %s, failed with error %d\n", debugstr_w(path), ret);
770             ret = ERROR_CANCELLED; /* Error has been already presented to user (not really yet!) */
771           }
772         }
773         return ret;
774 }
775
776 /*************************************************************************
777  * SHFindAttrW      [internal]
778  *
779  * Get the Attributes for a file or directory. The difference to GetAttributes()
780  * is that this function will also work for paths containing wildcard characters
781  * in its filename.
782
783  * PARAMS
784  *  path       [I]   path of directory or file to check
785  *  fileOnly   [I]   TRUE if only files should be found
786  *
787  * RETURNS
788  *  INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of
789  *  the first file or directory found otherwise
790  */
791 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly)
792 {
793         WIN32_FIND_DATAW wfd;
794         BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, wWildcardChars));
795         DWORD dwAttr = INVALID_FILE_ATTRIBUTES;
796         HANDLE hFind = FindFirstFileW(pName, &wfd);
797
798         TRACE("%s %d\n", debugstr_w(pName), fileOnly);
799         if (INVALID_HANDLE_VALUE != hFind)
800         {
801           do
802           {
803             if (b_FileMask && IsAttribDir(wfd.dwFileAttributes))
804                continue;
805             dwAttr = wfd.dwFileAttributes;
806             break;
807           }
808           while (FindNextFileW(hFind, &wfd));
809           FindClose(hFind);
810         }
811         return dwAttr;
812 }
813
814 /*************************************************************************
815  *
816  * SHNameTranslate HelperFunction for SHFileOperationA
817  *
818  * Translates a list of 0 terminated ASCII strings into Unicode. If *wString
819  * is NULL, only the necessary size of the string is determined and returned,
820  * otherwise the ASCII strings are copied into it and the buffer is increased
821  * to point to the location after the final 0 termination char.
822  */
823 static DWORD SHNameTranslate(LPWSTR* wString, LPCWSTR* pWToFrom, BOOL more)
824 {
825         DWORD size = 0, aSize = 0;
826         LPCSTR aString = (LPCSTR)*pWToFrom;
827
828         if (aString)
829         {
830           do
831           {
832             size = lstrlenA(aString) + 1;
833             aSize += size;
834             aString += size;
835           } while ((size != 1) && more);
836           /* The two sizes might be different in the case of multibyte chars */
837           size = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, 0);
838           if (*wString) /* only in the second loop */
839           {
840             MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, size);
841             *pWToFrom = *wString;
842             *wString += size;
843           }
844         }
845         return size;
846 }
847 /*************************************************************************
848  * SHFileOperationA          [SHELL32.@]
849  *
850  * Function to copy, move, delete and create one or more files with optional
851  * user prompts.
852  *
853  * PARAMS
854  *  lpFileOp   [I/O] pointer to a structure containing all the necessary information
855  *
856  * RETURNS
857  *  Success: ERROR_SUCCESS.
858  *  Failure: ERROR_CANCELLED.
859  *
860  * NOTES
861  *  exported by name
862  */
863 int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
864 {
865         SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp);
866         int retCode = 0;
867         DWORD size;
868         LPWSTR ForFree = NULL, /* we change wString in SHNameTranslate and can't use it for freeing */
869                wString = NULL; /* we change this in SHNameTranslate */
870
871         TRACE("\n");
872         if (FO_DELETE == (nFileOp.wFunc & FO_MASK))
873           nFileOp.pTo = NULL; /* we need a NULL or a valid pointer for translation */
874         if (!(nFileOp.fFlags & FOF_SIMPLEPROGRESS))
875           nFileOp.lpszProgressTitle = NULL; /* we need a NULL or a valid pointer for translation */
876         while (1) /* every loop calculate size, second translate also, if we have storage for this */
877         {
878           size = SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */
879           size += SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */
880           size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */
881
882           if (ForFree)
883           {
884             retCode = SHFileOperationW(&nFileOp);
885             HeapFree(GetProcessHeap(), 0, ForFree); /* we cannot use wString, it was changed */
886             break;
887           }
888           else
889           {
890             wString = ForFree = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
891             if (ForFree) continue;
892             retCode = ERROR_OUTOFMEMORY;
893             nFileOp.fAnyOperationsAborted = TRUE;
894             SetLastError(retCode);
895             return retCode;
896           }
897         }
898
899         lpFileOp->hNameMappings = nFileOp.hNameMappings;
900         lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
901         return retCode;
902 }
903
904 #define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
905
906 typedef struct
907 {
908     DWORD attributes;
909     LPWSTR szDirectory;
910     LPWSTR szFilename;
911     LPWSTR szFullPath;
912     BOOL bFromWildcard;
913     BOOL bFromRelative;
914     BOOL bExists;
915 } FILE_ENTRY;
916
917 typedef struct
918 {
919     FILE_ENTRY *feFiles;
920     DWORD num_alloc;
921     DWORD dwNumFiles;
922     BOOL bAnyFromWildcard;
923     BOOL bAnyDirectories;
924     BOOL bAnyDontExist;
925 } FILE_LIST;
926
927
928 static inline void grow_list(FILE_LIST *list)
929 {
930     FILE_ENTRY *new = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles,
931                                   list->num_alloc * 2 * sizeof(*new) );
932     list->feFiles = new;
933     list->num_alloc *= 2;
934 }
935
936 /* adds a file to the FILE_ENTRY struct
937  */
938 static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile)
939 {
940     DWORD dwLen = lstrlenW(szFile) + 1;
941     LPCWSTR ptr;
942
943     feFile->szFullPath = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
944     lstrcpyW(feFile->szFullPath, szFile);
945
946     ptr = StrRChrW(szFile, NULL, '\\');
947     if (ptr)
948     {
949         dwLen = ptr - szFile + 1;
950         feFile->szDirectory = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
951         lstrcpynW(feFile->szDirectory, szFile, dwLen);
952
953         dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1;
954         feFile->szFilename = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
955         lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */
956     }
957     feFile->bFromWildcard = FALSE;
958 }
959
960 static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName)
961 {
962     LPCWSTR ptr;
963     LPWSTR szFullPath;
964     DWORD dwDirLen, dwFullLen;
965
966     ptr = StrRChrW(szWildCard, NULL, '\\');
967     dwDirLen = ptr - szWildCard + 1;
968
969     dwFullLen = dwDirLen + lstrlenW(szFileName) + 1;
970     szFullPath = HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR));
971
972     lstrcpynW(szFullPath, szWildCard, dwDirLen + 1);
973     lstrcatW(szFullPath, szFileName);
974
975     return szFullPath;
976 }
977
978 static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex)
979 {
980     WIN32_FIND_DATAW wfd;
981     HANDLE hFile = FindFirstFileW(szFile, &wfd);
982     FILE_ENTRY *file;
983     LPWSTR szFullPath;
984     BOOL res;
985
986     if (hFile == INVALID_HANDLE_VALUE) return;
987
988     for (res = TRUE; res; res = FindNextFileW(hFile, &wfd))
989     {
990         if (IsDotDir(wfd.cFileName)) continue;
991         if (*pdwListIndex >= flList->num_alloc) grow_list( flList );
992         szFullPath = wildcard_to_file(szFile, wfd.cFileName);
993         file = &flList->feFiles[(*pdwListIndex)++];
994         add_file_to_entry(file, szFullPath);
995         file->bFromWildcard = TRUE;
996         file->attributes = wfd.dwFileAttributes;
997         if (IsAttribDir(file->attributes)) flList->bAnyDirectories = TRUE;
998         HeapFree(GetProcessHeap(), 0, szFullPath);
999     }
1000
1001     FindClose(hFile);
1002 }
1003
1004 /* takes the null-separated file list and fills out the FILE_LIST */
1005 static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles)
1006 {
1007     LPCWSTR ptr = szFiles;
1008     WCHAR szCurFile[MAX_PATH];
1009     DWORD i = 0;
1010
1011     if (!szFiles)
1012         return ERROR_INVALID_PARAMETER;
1013
1014     flList->bAnyFromWildcard = FALSE;
1015     flList->bAnyDirectories = FALSE;
1016     flList->bAnyDontExist = FALSE;
1017     flList->num_alloc = 32;
1018     flList->dwNumFiles = 0;
1019
1020     /* empty list */
1021     if (!szFiles[0])
1022         return ERROR_ACCESS_DENIED;
1023         
1024     flList->feFiles = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
1025                                 flList->num_alloc * sizeof(FILE_ENTRY));
1026
1027     while (*ptr)
1028     {
1029         if (i >= flList->num_alloc) grow_list( flList );
1030
1031         /* change relative to absolute path */
1032         if (PathIsRelativeW(ptr))
1033         {
1034             GetCurrentDirectoryW(MAX_PATH, szCurFile);
1035             PathCombineW(szCurFile, szCurFile, ptr);
1036             flList->feFiles[i].bFromRelative = TRUE;
1037         }
1038         else
1039         {
1040             lstrcpyW(szCurFile, ptr);
1041             flList->feFiles[i].bFromRelative = FALSE;
1042         }
1043
1044         /* parse wildcard files if they are in the filename */
1045         if (StrPBrkW(szCurFile, wWildcardChars))
1046         {
1047             parse_wildcard_files(flList, szCurFile, &i);
1048             flList->bAnyFromWildcard = TRUE;
1049             i--;
1050         }
1051         else
1052         {
1053             FILE_ENTRY *file = &flList->feFiles[i];
1054             add_file_to_entry(file, szCurFile);
1055             file->attributes = GetFileAttributesW( file->szFullPath );
1056             file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES);
1057             if (!file->bExists) flList->bAnyDontExist = TRUE;
1058             if (IsAttribDir(file->attributes)) flList->bAnyDirectories = TRUE;
1059         }
1060
1061         /* advance to the next string */
1062         ptr += lstrlenW(ptr) + 1;
1063         i++;
1064     }
1065     flList->dwNumFiles = i;
1066
1067     return S_OK;
1068 }
1069
1070 /* free the FILE_LIST */
1071 static void destroy_file_list(FILE_LIST *flList)
1072 {
1073     DWORD i;
1074
1075     if (!flList || !flList->feFiles)
1076         return;
1077
1078     for (i = 0; i < flList->dwNumFiles; i++)
1079     {
1080         HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory);
1081         HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename);
1082         HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath);
1083     }
1084
1085     HeapFree(GetProcessHeap(), 0, flList->feFiles);
1086 }
1087
1088 static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
1089 {
1090     WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1091     SHFILEOPSTRUCTW fileOp;
1092
1093     static const WCHAR wildCardFiles[] = {'*','.','*',0};
1094
1095     if (IsDotDir(feFrom->szFilename))
1096         return;
1097
1098     if (PathFileExistsW(szDestPath))
1099         PathCombineW(szTo, szDestPath, feFrom->szFilename);
1100     else
1101         lstrcpyW(szTo, szDestPath);
1102
1103     if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) {
1104         if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op))
1105         {
1106             /* Vista returns an ERROR_CANCELLED even if user pressed "No" */
1107             if (!op->bManyItems)
1108                 op->bCancelled = TRUE;
1109             return;
1110         }
1111     }
1112
1113     szTo[lstrlenW(szTo) + 1] = '\0';
1114     SHNotifyCreateDirectoryW(szTo, NULL);
1115
1116     PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
1117     szFrom[lstrlenW(szFrom) + 1] = '\0';
1118
1119     fileOp = *op->req;
1120     fileOp.pFrom = szFrom;
1121     fileOp.pTo = szTo;
1122     fileOp.fFlags &= ~FOF_MULTIDESTFILES; /* we know we're copying to one dir */
1123
1124     /* Don't ask the user about overwriting files when he accepted to overwrite the
1125        folder. FIXME: this is not exactly what Windows does - e.g. there would be
1126        an additional confirmation for a nested folder */
1127     fileOp.fFlags |= FOF_NOCONFIRMATION;  
1128
1129     SHFileOperationW(&fileOp);
1130 }
1131
1132 static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
1133 {
1134     if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo))
1135     {
1136         if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
1137             return 0;
1138     }
1139
1140     return SHNotifyCopyFileW(szFrom, szTo, FALSE) == 0;
1141 }
1142
1143 /* copy a file or directory to another directory */
1144 static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
1145 {
1146     if (!PathFileExistsW(feTo->szFullPath))
1147         SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);
1148
1149     if (IsAttribFile(feFrom->attributes))
1150     {
1151         WCHAR szDestPath[MAX_PATH];
1152
1153         PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1154         copy_file_to_file(op, feFrom->szFullPath, szDestPath);
1155     }
1156     else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
1157         copy_dir_to_dir(op, feFrom, feTo->szFullPath);
1158 }
1159
1160 static void create_dest_dirs(LPCWSTR szDestDir)
1161 {
1162     WCHAR dir[MAX_PATH];
1163     LPCWSTR ptr = StrChrW(szDestDir, '\\');
1164
1165     /* make sure all directories up to last one are created */
1166     while (ptr && (ptr = StrChrW(ptr + 1, '\\')))
1167     {
1168         lstrcpynW(dir, szDestDir, ptr - szDestDir + 1);
1169
1170         if (!PathFileExistsW(dir))
1171             SHNotifyCreateDirectoryW(dir, NULL);
1172     }
1173
1174     /* create last directory */
1175     if (!PathFileExistsW(szDestDir))
1176         SHNotifyCreateDirectoryW(szDestDir, NULL);
1177 }
1178
1179 /* the FO_COPY operation */
1180 static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST *flTo)
1181 {
1182     DWORD i;
1183     const FILE_ENTRY *entryToCopy;
1184     const FILE_ENTRY *fileDest = &flTo->feFiles[0];
1185
1186     if (flFrom->bAnyDontExist)
1187         return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1188
1189     if (flTo->dwNumFiles == 0)
1190     {
1191         /* If the destination is empty, SHFileOperation should use the current directory */
1192         WCHAR curdir[MAX_PATH+1];
1193
1194         GetCurrentDirectoryW(MAX_PATH, curdir);
1195         curdir[lstrlenW(curdir)+1] = 0;
1196
1197         destroy_file_list(flTo);
1198         ZeroMemory(flTo, sizeof(FILE_LIST));
1199         parse_file_list(flTo, curdir);
1200         fileDest = &flTo->feFiles[0];
1201     }
1202
1203     if (op->req->fFlags & FOF_MULTIDESTFILES)
1204     {
1205         if (flFrom->bAnyFromWildcard)
1206             return ERROR_CANCELLED;
1207
1208         if (flFrom->dwNumFiles != flTo->dwNumFiles)
1209         {
1210             if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
1211                 return ERROR_CANCELLED;
1212
1213             /* Free all but the first entry. */
1214             for (i = 1; i < flTo->dwNumFiles; i++)
1215             {
1216                 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory);
1217                 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename);
1218                 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath);
1219             }
1220
1221             flTo->dwNumFiles = 1;
1222         }
1223         else if (IsAttribDir(fileDest->attributes))
1224         {
1225             for (i = 1; i < flTo->dwNumFiles; i++)
1226                 if (!IsAttribDir(flTo->feFiles[i].attributes) ||
1227                     !IsAttribDir(flFrom->feFiles[i].attributes))
1228                 {
1229                     return ERROR_CANCELLED;
1230                 }
1231         }
1232     }
1233     else if (flFrom->dwNumFiles != 1)
1234     {
1235         if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
1236             return ERROR_CANCELLED;
1237
1238         if (PathFileExistsW(fileDest->szFullPath) &&
1239             IsAttribFile(fileDest->attributes))
1240         {
1241             return ERROR_CANCELLED;
1242         }
1243
1244         if (flTo->dwNumFiles == 1 && fileDest->bFromRelative &&
1245             !PathFileExistsW(fileDest->szFullPath))
1246         {
1247             return ERROR_CANCELLED;
1248         }
1249     }
1250
1251     for (i = 0; i < flFrom->dwNumFiles; i++)
1252     {
1253         entryToCopy = &flFrom->feFiles[i];
1254
1255         if ((op->req->fFlags & FOF_MULTIDESTFILES) &&
1256             flTo->dwNumFiles > 1)
1257         {
1258             fileDest = &flTo->feFiles[i];
1259         }
1260
1261         if (IsAttribDir(entryToCopy->attributes) &&
1262             !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory))
1263         {
1264             return ERROR_SUCCESS;
1265         }
1266
1267         create_dest_dirs(fileDest->szDirectory);
1268
1269         if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath))
1270         {
1271             if (IsAttribFile(entryToCopy->attributes))
1272                 return ERROR_NO_MORE_SEARCH_HANDLES;
1273             else
1274                 return ERROR_SUCCESS;
1275         }
1276
1277         if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
1278             IsAttribDir(fileDest->attributes))
1279         {
1280             copy_to_dir(op, entryToCopy, fileDest);
1281         }
1282         else if (IsAttribDir(entryToCopy->attributes))
1283         {
1284             copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath);
1285         }
1286         else
1287         {
1288             if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath))
1289             {
1290                 op->req->fAnyOperationsAborted = TRUE;
1291                 return ERROR_CANCELLED;
1292             }
1293         }
1294
1295         /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
1296         if (op->bCancelled)
1297             return ERROR_CANCELLED;
1298     }
1299
1300     /* Vista return code. On XP if the used pressed "No" for the last item,
1301      * ERROR_ARENA_TRASHED would be returned */
1302     return ERROR_SUCCESS;
1303 }
1304
1305 static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom)
1306 {
1307     if (flFrom->dwNumFiles > 1)
1308     {
1309         WCHAR tmp[8];
1310         const WCHAR format[] = {'%','d',0};
1311
1312         wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), format, flFrom->dwNumFiles);
1313         return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL);
1314     }
1315     else
1316     {
1317         const FILE_ENTRY *fileEntry = &flFrom->feFiles[0];
1318
1319         if (IsAttribFile(fileEntry->attributes))
1320             return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL);
1321         else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1322             return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL);
1323     }
1324     return TRUE;
1325 }
1326
1327 /* the FO_DELETE operation */
1328 static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
1329 {
1330     const FILE_ENTRY *fileEntry;
1331     DWORD i;
1332     BOOL bPathExists;
1333     BOOL bTrash;
1334
1335     if (!flFrom->dwNumFiles)
1336         return ERROR_SUCCESS;
1337
1338     /* Windows also checks only the first item */
1339     bTrash = (lpFileOp->fFlags & FOF_ALLOWUNDO)
1340         && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath);
1341
1342     if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (!bTrash && lpFileOp->fFlags & FOF_WANTNUKEWARNING))
1343         if (!confirm_delete_list(lpFileOp->hwnd, lpFileOp->fFlags, bTrash, flFrom))
1344         {
1345             lpFileOp->fAnyOperationsAborted = TRUE;
1346             return 0;
1347         }
1348
1349     for (i = 0; i < flFrom->dwNumFiles; i++)
1350     {
1351         fileEntry = &flFrom->feFiles[i];
1352
1353         if (!IsAttribFile(fileEntry->attributes) &&
1354             (lpFileOp->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1355             continue;
1356
1357         if (bTrash)
1358         {
1359             BOOL bDelete;
1360             if (TRASH_TrashFile(fileEntry->szFullPath))
1361                 continue;
1362
1363             /* Note: Windows silently deletes the file in such a situation, we show a dialog */
1364             if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (lpFileOp->fFlags & FOF_WANTNUKEWARNING))
1365                 bDelete = SHELL_ConfirmDialogW(lpFileOp->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
1366             else
1367                 bDelete = TRUE;
1368
1369             if (!bDelete)
1370             {
1371                 lpFileOp->fAnyOperationsAborted = TRUE;
1372                 break;
1373             }
1374         }
1375         
1376         /* delete the file or directory */
1377         if (IsAttribFile(fileEntry->attributes))
1378             bPathExists = DeleteFileW(fileEntry->szFullPath);
1379         else
1380             bPathExists = SHELL_DeleteDirectoryW(lpFileOp->hwnd, fileEntry->szFullPath, FALSE);
1381
1382         if (!bPathExists)
1383             return ERROR_PATH_NOT_FOUND;
1384     }
1385
1386     return ERROR_SUCCESS;
1387 }
1388
1389 static void move_dir_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
1390 {
1391     WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1392     SHFILEOPSTRUCTW fileOp;
1393
1394     static const WCHAR wildCardFiles[] = {'*','.','*',0};
1395
1396     if (IsDotDir(feFrom->szFilename))
1397         return;
1398
1399     SHNotifyCreateDirectoryW(szDestPath, NULL);
1400
1401     PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
1402     szFrom[lstrlenW(szFrom) + 1] = '\0';
1403
1404     lstrcpyW(szTo, szDestPath);
1405     szTo[lstrlenW(szDestPath) + 1] = '\0';
1406
1407     fileOp = *lpFileOp;
1408     fileOp.pFrom = szFrom;
1409     fileOp.pTo = szTo;
1410
1411     SHFileOperationW(&fileOp);
1412 }
1413
1414 /* moves a file or directory to another directory */
1415 static void move_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
1416 {
1417     WCHAR szDestPath[MAX_PATH];
1418
1419     PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1420
1421     if (IsAttribFile(feFrom->attributes))
1422         SHNotifyMoveFileW(feFrom->szFullPath, szDestPath);
1423     else if (!(lpFileOp->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
1424         move_dir_to_dir(lpFileOp, feFrom, szDestPath);
1425 }
1426
1427 /* the FO_MOVE operation */
1428 static HRESULT move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo)
1429 {
1430     DWORD i;
1431     const FILE_ENTRY *entryToMove;
1432     const FILE_ENTRY *fileDest;
1433
1434     if (!flFrom->dwNumFiles || !flTo->dwNumFiles)
1435         return ERROR_CANCELLED;
1436
1437     if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
1438         flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1)
1439     {
1440         return ERROR_CANCELLED;
1441     }
1442
1443     if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
1444         !flFrom->bAnyDirectories &&
1445         flFrom->dwNumFiles > flTo->dwNumFiles)
1446     {
1447         return ERROR_CANCELLED;
1448     }
1449
1450     if (!PathFileExistsW(flTo->feFiles[0].szDirectory))
1451         return ERROR_CANCELLED;
1452
1453     if ((lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
1454         flFrom->dwNumFiles != flTo->dwNumFiles)
1455     {
1456         return ERROR_CANCELLED;
1457     }
1458
1459     fileDest = &flTo->feFiles[0];
1460     for (i = 0; i < flFrom->dwNumFiles; i++)
1461     {
1462         entryToMove = &flFrom->feFiles[i];
1463
1464         if (lpFileOp->fFlags & FOF_MULTIDESTFILES)
1465             fileDest = &flTo->feFiles[i];
1466
1467         if (!PathFileExistsW(fileDest->szDirectory))
1468             return ERROR_CANCELLED;
1469
1470         if (fileDest->bExists && IsAttribDir(fileDest->attributes))
1471             move_to_dir(lpFileOp, entryToMove, fileDest);
1472         else
1473             SHNotifyMoveFileW(entryToMove->szFullPath, fileDest->szFullPath);
1474     }
1475
1476     return ERROR_SUCCESS;
1477 }
1478
1479 /* the FO_RENAME files */
1480 static HRESULT rename_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo)
1481 {
1482     const FILE_ENTRY *feFrom;
1483     const FILE_ENTRY *feTo;
1484
1485     if (flFrom->dwNumFiles != 1)
1486         return ERROR_GEN_FAILURE;
1487
1488     if (flTo->dwNumFiles != 1)
1489         return ERROR_CANCELLED;
1490
1491     feFrom = &flFrom->feFiles[0];
1492     feTo= &flTo->feFiles[0];
1493
1494     /* fail if destination doesn't exist */
1495     if (!feFrom->bExists)
1496         return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1497
1498     /* fail if destination already exists */
1499     if (feTo->bExists)
1500         return ERROR_ALREADY_EXISTS;
1501
1502     return SHNotifyMoveFileW(feFrom->szFullPath, feTo->szFullPath);
1503 }
1504
1505 /* alert the user if an unsupported flag is used */
1506 static void check_flags(FILEOP_FLAGS fFlags)
1507 {
1508     WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS |
1509         FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE |
1510         FOF_RENAMEONCOLLISION | FOF_WANTMAPPINGHANDLE;
1511
1512     if (fFlags & wUnsupportedFlags)
1513         FIXME("Unsupported flags: %04x\n", fFlags);
1514 }
1515
1516 /*************************************************************************
1517  * SHFileOperationW          [SHELL32.@]
1518  *
1519  * See SHFileOperationA
1520  */
1521 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
1522 {
1523     FILE_OPERATION op;
1524     FILE_LIST flFrom, flTo;
1525     int ret = 0;
1526
1527     if (!lpFileOp)
1528         return ERROR_INVALID_PARAMETER;
1529
1530     check_flags(lpFileOp->fFlags);
1531
1532     ZeroMemory(&flFrom, sizeof(FILE_LIST));
1533     ZeroMemory(&flTo, sizeof(FILE_LIST));
1534
1535     if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom)))
1536         return ret;
1537
1538     if (lpFileOp->wFunc != FO_DELETE)
1539         parse_file_list(&flTo, lpFileOp->pTo);
1540
1541     ZeroMemory(&op, sizeof(op));
1542     op.req = lpFileOp;
1543     op.bManyItems = (flFrom.dwNumFiles > 1);
1544
1545     switch (lpFileOp->wFunc)
1546     {
1547         case FO_COPY:
1548             ret = copy_files(&op, &flFrom, &flTo);
1549             break;
1550         case FO_DELETE:
1551             ret = delete_files(lpFileOp, &flFrom);
1552             break;
1553         case FO_MOVE:
1554             ret = move_files(lpFileOp, &flFrom, &flTo);
1555             break;
1556         case FO_RENAME:
1557             ret = rename_files(lpFileOp, &flFrom, &flTo);
1558             break;
1559         default:
1560             ret = ERROR_INVALID_PARAMETER;
1561             break;
1562     }
1563
1564     destroy_file_list(&flFrom);
1565
1566     if (lpFileOp->wFunc != FO_DELETE)
1567         destroy_file_list(&flTo);
1568
1569     if (ret == ERROR_CANCELLED)
1570         lpFileOp->fAnyOperationsAborted = TRUE;
1571
1572     return ret;
1573 }
1574
1575 #define SHDSA_GetItemCount(hdsa) (*(int*)(hdsa))
1576
1577 /*************************************************************************
1578  * SHFreeNameMappings      [shell32.246]
1579  *
1580  * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE
1581  * was specified.
1582  *
1583  * PARAMS
1584  *  hNameMapping [I] handle to the name mappings used during renaming of files
1585  *
1586  * RETURNS
1587  *  Nothing
1588  */
1589 void WINAPI SHFreeNameMappings(HANDLE hNameMapping)
1590 {
1591         if (hNameMapping)
1592         {
1593           int i = SHDSA_GetItemCount((HDSA)hNameMapping) - 1;
1594
1595           for (; i>= 0; i--)
1596           {
1597             LPSHNAMEMAPPINGW lp = DSA_GetItemPtr(hNameMapping, i);
1598
1599             SHFree(lp->pszOldPath);
1600             SHFree(lp->pszNewPath);
1601           }
1602           DSA_Destroy(hNameMapping);
1603         }
1604 }
1605
1606 /*************************************************************************
1607  * SheGetDirA [SHELL32.@]
1608  *
1609  * drive = 0: returns the current directory path
1610  * drive > 0: returns the current directory path of the specified drive
1611  *            drive=1 -> A:  drive=2 -> B:  ...
1612  * returns 0 if successful
1613 */
1614 DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer)
1615 {
1616     WCHAR org_path[MAX_PATH];
1617     DWORD ret;
1618     char drv_path[3];
1619
1620     /* change current directory to the specified drive */
1621     if (drive) {
1622         strcpy(drv_path, "A:");
1623         drv_path[0] += (char)drive-1;
1624
1625         GetCurrentDirectoryW(MAX_PATH, org_path);
1626
1627         SetCurrentDirectoryA(drv_path);
1628     }
1629
1630     /* query current directory path of the specified drive */
1631     ret = GetCurrentDirectoryA(MAX_PATH, buffer);
1632
1633     /* back to the original drive */
1634     if (drive)
1635         SetCurrentDirectoryW(org_path);
1636
1637     if (!ret)
1638         return GetLastError();
1639
1640     return 0;
1641 }
1642
1643 /*************************************************************************
1644  * SheGetDirW [SHELL32.@]
1645  *
1646  * drive = 0: returns the current directory path
1647  * drive > 0: returns the current directory path of the specified drive
1648  *            drive=1 -> A:  drive=2 -> B:  ...
1649  * returns 0 if successful
1650  */
1651 DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer)
1652 {
1653     WCHAR org_path[MAX_PATH];
1654     DWORD ret;
1655     char drv_path[3];
1656
1657     /* change current directory to the specified drive */
1658     if (drive) {
1659         strcpy(drv_path, "A:");
1660         drv_path[0] += (char)drive-1;
1661
1662         GetCurrentDirectoryW(MAX_PATH, org_path);
1663
1664         SetCurrentDirectoryA(drv_path);
1665     }
1666
1667     /* query current directory path of the specified drive */
1668     ret = GetCurrentDirectoryW(MAX_PATH, buffer);
1669
1670     /* back to the original drive */
1671     if (drive)
1672         SetCurrentDirectoryW(org_path);
1673
1674     if (!ret)
1675         return GetLastError();
1676
1677     return 0;
1678 }
1679
1680 /*************************************************************************
1681  * SheChangeDirA [SHELL32.@]
1682  *
1683  * changes the current directory to the specified path
1684  * and returns 0 if successful
1685  */
1686 DWORD WINAPI SheChangeDirA(LPSTR path)
1687 {
1688     if (SetCurrentDirectoryA(path))
1689         return 0;
1690     else
1691         return GetLastError();
1692 }
1693
1694 /*************************************************************************
1695  * SheChangeDirW [SHELL32.@]
1696  *
1697  * changes the current directory to the specified path
1698  * and returns 0 if successful
1699  */
1700 DWORD WINAPI SheChangeDirW(LPWSTR path)
1701 {
1702     if (SetCurrentDirectoryW(path))
1703         return 0;
1704     else
1705         return GetLastError();
1706 }
1707
1708 /*************************************************************************
1709  * IsNetDrive                   [SHELL32.66]
1710  */
1711 BOOL WINAPI IsNetDrive(DWORD drive)
1712 {
1713         char root[4];
1714         strcpy(root, "A:\\");
1715         root[0] += (char)drive;
1716         return (GetDriveTypeA(root) == DRIVE_REMOTE);
1717 }
1718
1719
1720 /*************************************************************************
1721  * RealDriveType                [SHELL32.524]
1722  */
1723 INT WINAPI RealDriveType(INT drive, BOOL bQueryNet)
1724 {
1725     char root[] = "A:\\";
1726     root[0] += (char)drive;
1727     return GetDriveTypeA(root);
1728 }
1729
1730 /***********************************************************************
1731  *              SHPathPrepareForWriteA (SHELL32.@)
1732  */
1733 HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags)
1734 {
1735     WCHAR wpath[MAX_PATH];
1736     MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH);
1737     return SHPathPrepareForWriteW(hwnd, modless, wpath, flags);
1738 }
1739
1740 /***********************************************************************
1741  *              SHPathPrepareForWriteW (SHELL32.@)
1742  */
1743 HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags)
1744 {
1745     DWORD res;
1746     DWORD err;
1747     LPCWSTR realpath;
1748     int len;
1749     WCHAR* last_slash;
1750     WCHAR* temppath=NULL;
1751
1752     TRACE("%p %p %s 0x%80x\n", hwnd, modless, debugstr_w(path), flags);
1753
1754     if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME))
1755         FIXME("unimplemented flags 0x%08x\n", flags);
1756
1757     /* cut off filename if necessary */
1758     if (flags & SHPPFW_IGNOREFILENAME)
1759     {
1760         last_slash = StrRChrW(path, NULL, '\\');
1761         if (last_slash == NULL)
1762             len = 1;
1763         else
1764             len = last_slash - path + 1;
1765         temppath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1766         if (!temppath)
1767             return E_OUTOFMEMORY;
1768         StrCpyNW(temppath, path, len);
1769         realpath = temppath;
1770     }
1771     else
1772     {
1773         realpath = path;
1774     }
1775
1776     /* try to create the directory if asked to */
1777     if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE))
1778     {
1779         if (flags & SHPPFW_ASKDIRCREATE)
1780             FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n");
1781
1782         SHCreateDirectoryExW(0, realpath, NULL);
1783     }
1784
1785     /* check if we can access the directory */
1786     res = GetFileAttributesW(realpath);
1787
1788     HeapFree(GetProcessHeap(), 0, temppath);
1789
1790     if (res == INVALID_FILE_ATTRIBUTES)
1791     {
1792         err = GetLastError();
1793         if (err == ERROR_FILE_NOT_FOUND)
1794             return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
1795         return HRESULT_FROM_WIN32(err);
1796     }
1797     else if (res & FILE_ATTRIBUTE_DIRECTORY)
1798         return S_OK;
1799     else
1800         return HRESULT_FROM_WIN32(ERROR_DIRECTORY);
1801 }