source: trunk/src/shell32/shlfileop.c@ 10367

Last change on this file since 10367 was 10314, checked in by sandervl, 22 years ago

DT: Resource updates ; Shell file operations updates

File size: 52.1 KB
Line 
1/*
2 * SHFileOperation
3 *
4 * Copyright 2000 Juergen Schmied
5 * Copyright 2002 Andriy Palamarchuk
6 * Copyright 2002-2003 Dietrich Teickner (from Odin)
7 * Copyright 2002-2003 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 *
23 * !!! shlfileop.c is shared source between wine and odin, do not remove
24 * #ifdef __WIN32OS2__ .. lines
25 */
26
27#include "config.h"
28#include "wine/port.h"
29
30#include <stdarg.h>
31#include <string.h>
32#include <ctype.h>
33
34#include "windef.h"
35#include "winbase.h"
36#include "winreg.h"
37#include "shellapi.h"
38#include "wingdi.h"
39#include "winuser.h"
40#include "shlobj.h"
41#include "shresdef.h"
42#define NO_SHLWAPI_STREAM
43#include "shlwapi.h"
44#include "shell32_main.h"
45#include "undocshell.h"
46#include "wine/unicode.h"
47#include "wine/debug.h"
48
49WINE_DEFAULT_DEBUG_CHANNEL(shell);
50
51#define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY))
52#define IsAttrib(x,y) ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y)))
53#define IsAttribDir(x) IsAttrib(x,FILE_ATTRIBUTE_DIRECTORY)
54
55#define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
56
57#define FO_MASK 0xFF
58#define FO_LevelShift 8
59#define HIGH_ADR (LPWSTR)0xffffffff
60
61#define FOI_NeverOverwrite 2
62
63CHAR aWildcardFile[] = {'*',0};
64WCHAR wWildcardChars[] = {'?','*',0};
65#define wWildcardFile &wWildcardChars[1]
66WCHAR wBackslash[] = {'\\',0};
67enum { none = 0, w95, w95b, nt351, nt40, w98, w98se, wMe, w2k, wXp};
68static int WOsVers = none; /* for versionsdepended conditions */
69static int retCodeIsInvalid = 0x75; /* w95?,w98se, nt <= 4? */
70static LPCSTR cFO_String [] = {"FO_????","FO_MOVE","FO_COPY","FO_DELETE","FO_RENAME"};
71
72static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec);
73static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
74static DWORD SHNotifyRemoveDirectoryA(LPCSTR path);
75static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
76static DWORD SHNotifyDeleteFileA(LPCSTR path);
77static DWORD SHNotifyDeleteFileW(LPCWSTR path);
78static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest);
79static DWORD SHNotifyCopyFileW(LPCWSTR, LPCWSTR, BOOL);
80
81typedef struct
82{
83 UINT caption_resource_id, text_resource_id;
84} SHELL_ConfirmIDstruc;
85
86static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids)
87{
88 switch (nKindOfDialog) {
89 case ASK_DELETE_FILE:
90 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
91 ids->text_resource_id = IDS_DELETEITEM_TEXT;
92 return TRUE;
93 case ASK_DELETE_FOLDER:
94 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
95 ids->text_resource_id = IDS_DELETEITEM_TEXT;
96 return TRUE;
97 case ASK_DELETE_MULTIPLE_ITEM:
98 ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
99 ids->text_resource_id = IDS_DELETEMULTIPLE_TEXT;
100 return TRUE;
101 case ASK_OVERWRITE_FILE:
102 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION;
103 ids->text_resource_id = IDS_OVERWRITEFILE_TEXT;
104 return TRUE;
105 default:
106 FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
107 }
108 return FALSE;
109}
110
111BOOL SHELL_ConfirmDialog(int nKindOfDialog, LPCSTR szDir)
112{
113 CHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
114 SHELL_ConfirmIDstruc ids;
115
116 if (!SHELL_ConfirmIDs(nKindOfDialog, &ids))
117 return FALSE;
118
119 LoadStringA(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption));
120 LoadStringA(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText));
121
122 FormatMessageA(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
123 szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)&szDir);
124
125 return (IDOK == MessageBoxA(GetActiveWindow(), szBuffer, szCaption, MB_OKCANCEL | MB_ICONEXCLAMATION));
126}
127
128BOOL SHELL_ConfirmDialogW(int nKindOfDialog, LPCWSTR szDir)
129{
130 WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
131 SHELL_ConfirmIDstruc ids;
132
133 if (!SHELL_ConfirmIDs(nKindOfDialog, &ids))
134 return FALSE;
135
136 LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption));
137 LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText));
138
139 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
140 szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)&szDir);
141
142 return (IDOK == MessageBoxW(GetActiveWindow(), szBuffer, szCaption, MB_OKCANCEL | MB_ICONEXCLAMATION));
143}
144
145/**************************************************************************
146 * SHELL_DeleteDirectoryA() [internal]
147 *
148 * like rm -r
149 */
150BOOL SHELL_DeleteDirectoryA(LPCSTR pszDir, BOOL bShowUI)
151{
152 BOOL ret = TRUE;
153 HANDLE hFind;
154 WIN32_FIND_DATAA wfd;
155 char szTemp[MAX_PATH];
156
157 /* Make sure the directory exists before eventually prompting the user */
158 PathCombineA(szTemp, pszDir, aWildcardFile);
159 hFind = FindFirstFileA(szTemp, &wfd);
160 if (hFind == INVALID_HANDLE_VALUE)
161 return FALSE;
162
163 if (!bShowUI || SHELL_ConfirmDialog(ASK_DELETE_FOLDER, pszDir))
164 {
165 do
166 {
167 LPSTR lp = wfd.cAlternateFileName;
168 if (!lp[0])
169 lp = wfd.cFileName;
170 if (IsDotDir(lp))
171 continue;
172 PathCombineA(szTemp, pszDir, lp);
173 if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
174 ret = SHELL_DeleteDirectoryA(szTemp, FALSE);
175 else
176 ret = (SHNotifyDeleteFileA(szTemp) == ERROR_SUCCESS);
177 } while (ret && FindNextFileA(hFind, &wfd));
178 }
179 FindClose(hFind);
180 if (ret)
181 ret = (SHNotifyRemoveDirectoryA(pszDir) == ERROR_SUCCESS);
182 return ret;
183}
184
185BOOL SHELL_DeleteDirectoryW(LPCWSTR pszDir, BOOL bShowUI)
186{
187 BOOL ret = TRUE;
188 HANDLE hFind;
189 WIN32_FIND_DATAW wfd;
190 WCHAR szTemp[MAX_PATH];
191
192 /* Make sure the directory exists before eventually prompting the user */
193 PathCombineW(szTemp, pszDir, wWildcardFile);
194 hFind = FindFirstFileW(szTemp, &wfd);
195 if (hFind == INVALID_HANDLE_VALUE)
196 return FALSE;
197
198 if (!bShowUI || SHELL_ConfirmDialogW(ASK_DELETE_FOLDER, pszDir))
199
200 {
201 do
202 {
203 LPWSTR lp = wfd.cAlternateFileName;
204 if (!lp[0])
205 lp = wfd.cFileName;
206 if (IsDotDir(lp))
207 continue;
208 PathCombineW(szTemp, pszDir, lp);
209 if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
210 ret = SHELL_DeleteDirectoryW(szTemp, FALSE);
211 else
212 ret = (SHNotifyDeleteFileW(szTemp) == ERROR_SUCCESS);
213 } while (ret && FindNextFileW(hFind, &wfd));
214 }
215 FindClose(hFind);
216 if (ret)
217 ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS);
218 return ret;
219}
220
221/**************************************************************************
222 * SHELL_DeleteFileA() [internal]
223 */
224BOOL SHELL_DeleteFileA(LPCSTR pszFile, BOOL bShowUI)
225{
226 if (bShowUI && !SHELL_ConfirmDialog(ASK_DELETE_FILE, pszFile))
227 return FALSE;
228
229 return (SHNotifyDeleteFileA(pszFile) == ERROR_SUCCESS);
230}
231
232BOOL SHELL_DeleteFileW(LPCWSTR pszFile, BOOL bShowUI)
233{
234 if (bShowUI && !SHELL_ConfirmDialogW(ASK_DELETE_FILE, pszFile))
235 return FALSE;
236
237 return (SHNotifyDeleteFileW(pszFile) == ERROR_SUCCESS);
238}
239
240/**************************************************************************
241 * Win32CreateDirectory [SHELL32.93]
242 *
243 * Creates a directory. Also triggers a change notify if one exists.
244 *
245 * PARAMS
246 * path [I] path to directory to create
247 *
248 * RETURNS
249 * TRUE if successful, FALSE otherwise
250 *
251 * NOTES:
252 * Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
253 * This is Unicode on NT/2000
254 */
255static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec)
256{
257 WCHAR wPath[MAX_PATH];
258 TRACE("(%s, %p)\n", debugstr_a(path), sec);
259
260 MultiByteToWideChar(CP_ACP, 0, path, -1, wPath, MAX_PATH);
261 return SHNotifyCreateDirectoryW(wPath, sec);
262}
263
264static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
265{
266 TRACE("(%s, %p)\n", debugstr_w(path), sec);
267
268 if (StrPBrkW(path, wWildcardChars))
269 {
270 /* FIXME: This test is necessary since our CreateDirectory implementation
271 does create directories with wildcard chars without objection. Once this
272 is fixed, this can go away. */
273 SetLastError(ERROR_INVALID_NAME);
274 if (w98se >= WOsVers) /* wMe ? */
275 return ERROR_FILE_NOT_FOUND; /* w98se */
276 return ERROR_INVALID_NAME; /* w2k */
277 }
278
279 if (CreateDirectoryW(path, sec))
280 {
281 SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
282 return ERROR_SUCCESS;
283 }
284 return GetLastError();
285}
286
287BOOL WINAPI Win32CreateDirectoryAW(LPCVOID path, LPSECURITY_ATTRIBUTES sec)
288{
289 if (SHELL_OsIsUnicode())
290 return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS);
291 return (SHNotifyCreateDirectoryA(path, sec) == ERROR_SUCCESS);
292}
293
294/************************************************************************
295 * Win32RemoveDirectory [SHELL32.94]
296 *
297 * Deletes a directory. Also triggers a change notify if one exists.
298 *
299 * PARAMS
300 * path [I] path to directory to delete
301 *
302 * RETURNS
303 * ERROR_SUCCESS if successful
304 *
305 * NOTES:
306 * Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
307 * This is Unicode on NT/2000
308 */
309
310static DWORD SHNotifyRemoveDirectoryA(LPCSTR path)
311{
312 WCHAR wPath[MAX_PATH];
313 TRACE("(%s)\n", debugstr_a(path));
314
315 MultiByteToWideChar(CP_ACP, 0, path, -1, wPath, MAX_PATH);
316 return SHNotifyRemoveDirectoryW(wPath);
317}
318
319/***********************************************************************/
320
321static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path)
322{
323 BOOL ret;
324 TRACE("(%s)\n", debugstr_w(path));
325
326 ret = RemoveDirectoryW(path);
327 if (!ret)
328 {
329 /* Directory may be write protected */
330 DWORD dwAttr = GetFileAttributesW(path);
331 if (IsAttrib(dwAttr,FILE_ATTRIBUTE_READONLY))
332 if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY))
333 ret = RemoveDirectoryW(path);
334 }
335 if (ret)
336 {
337 SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL);
338 return ERROR_SUCCESS;
339 }
340 return GetLastError();
341}
342
343/***********************************************************************/
344
345BOOL WINAPI Win32RemoveDirectoryAW(LPCVOID path)
346{
347 if (SHELL_OsIsUnicode())
348 return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
349 return (SHNotifyRemoveDirectoryA(path) == ERROR_SUCCESS);
350}
351
352/************************************************************************
353 * Win32DeleteFile [SHELL32.164]
354 *
355 * Deletes a file. Also triggers a change notify if one exists.
356 *
357 * PARAMS
358 * path [I] path to file to delete
359 *
360 * RETURNS
361 * TRUE if successful, FALSE otherwise
362 *
363 * NOTES:
364 * Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
365 * This is Unicode on NT/2000
366 */
367
368static DWORD SHNotifyDeleteFileA(LPCSTR path)
369{
370 WCHAR wPath[MAX_PATH];
371 TRACE("(%s)\n", debugstr_a(path));
372
373 MultiByteToWideChar(CP_ACP, 0, path, -1, wPath, MAX_PATH);
374 return SHNotifyDeleteFileW(wPath);
375}
376
377/***********************************************************************/
378
379static DWORD SHNotifyDeleteFileW(LPCWSTR path)
380{
381 BOOL ret;
382
383 TRACE("(%s)\n", debugstr_w(path));
384
385 ret = DeleteFileW(path);
386 if (!ret)
387 {
388 /* File may be write protected or a system file */
389 DWORD dwAttr = GetFileAttributesW(path);
390 if (IsAttrib(dwAttr,FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
391 if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
392 ret = DeleteFileW(path);
393 }
394 if (ret)
395 {
396 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
397 return ERROR_SUCCESS;
398 }
399 return GetLastError();
400}
401
402/***********************************************************************/
403
404DWORD WINAPI Win32DeleteFileAW(LPCVOID path)
405{
406 if (SHELL_OsIsUnicode())
407 return (SHNotifyDeleteFileW(path) == ERROR_SUCCESS);
408 return (SHNotifyDeleteFileA(path) == ERROR_SUCCESS);
409}
410
411/************************************************************************
412 * SHNotifyMoveFile [internal]
413 *
414 * Moves a file. Also triggers a change notify if one exists.
415 *
416 * PARAMS
417 * src [I] path to source file to move
418 * dest [I] path to target file to move to
419 *
420 * RETURNS
421 * NO_ERROR if successful, or an error code otherwise
422 */
423static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest)
424{
425 BOOL ret = FALSE;
426
427 TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
428
429 if (StrPBrkW(dest, wWildcardChars))
430 {
431 /* FIXME: This test is currently necessary since our MoveFile
432 implementation does create files with wildcard characters
433 without objection!! Once this is fixed, this here can go away. */
434 SetLastError(ERROR_INVALID_NAME);
435 if (w98se >= WOsVers) /* wMe ? */
436 return ERROR_FILE_NOT_FOUND; /* w98se */
437 return ERROR_INVALID_NAME; /* w2k,wXp */
438 }
439
440 ret = MoveFileW(src, dest);
441 if (!ret)
442 {
443 /* Source file may be write protected or a system file */
444 DWORD dwAttr = GetFileAttributesW(src);
445 if (IsAttrib(dwAttr,FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
446 if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
447 ret = MoveFileW(src, dest);
448 }
449 if (ret)
450 {
451 SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW, src, dest);
452 return ERROR_SUCCESS;
453 }
454 return GetLastError();
455}
456
457/************************************************************************
458 * SHNotifyCopyFile [internal]
459 *
460 * Copies a file. Also triggers a change notify if one exists.
461 *
462 * PARAMS
463 * src [I] path to source file to move
464 * dest [I] path to target file to move to
465 * b_nOverWrt [I] if TRUE, the target file will not be overwritten, if a
466 * file with this name already exists
467 *
468 * RETURNS
469 * NO_ERROR if successful, or an error code otherwise
470 */
471static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL b_nOverWrt)
472{
473 BOOL ret;
474
475 TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), b_nOverWrt ? "OverWriteIfExists" : "");
476
477 ret = CopyFileW(src, dest, b_nOverWrt);
478 if (ret)
479 {
480 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
481 return ERROR_SUCCESS;
482 }
483 return GetLastError();
484}
485
486
487/*************************************************************************
488 * SHCreateDirectory [SHELL32.165]
489 *
490 * Create a directory at the specified location
491 *
492 * PARAMS
493 * hWnd [I]
494 * path [I] path of directory to create
495 *
496 * RETURNS
497 * ERRROR_SUCCESS or one of the following values:
498 * ERROR_BAD_PATHNAME if the path is relative
499 * ERROR_INVLID_NAME if the path contains invalid chars
500 * ERROR_ALREADY_EXISTS when the directory already exists
501 * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
502 *
503 * NOTES
504 * exported by ordinal
505 * Win9x exports ANSI
506 * WinNT/2000 exports Unicode
507 */
508DWORD WINAPI SHCreateDirectory(HWND hWnd, LPCVOID path)
509{
510 if (SHELL_OsIsUnicode())
511 return SHCreateDirectoryExW(hWnd, path, NULL);
512 return SHCreateDirectoryExA(hWnd, path, NULL);
513}
514
515/*************************************************************************
516 * SHCreateDirectoryExA [SHELL32.@]
517 *
518 * Create a directory at the specified location
519 *
520 * PARAMS
521 * hWnd [I]
522 * path [I] path of directory to create
523 * sec [I] security attributes to use or NULL
524 *
525 * RETURNS
526 * ERRROR_SUCCESS or one of the following values:
527 * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative
528 * ERROR_INVLID_NAME if the path contains invalid chars
529 * ? ERROR_FILE_EXISTS when a file with that name exists
530 * ERROR_ALREADY_EXISTS when the directory already exists
531 * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
532 */
533DWORD WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
534{
535 WCHAR wPath[MAX_PATH];
536 TRACE("(%p, %s, %p)\n",hWnd, debugstr_a(path), sec);
537
538 MultiByteToWideChar(CP_ACP, 0, path, -1, wPath, MAX_PATH);
539 return SHCreateDirectoryExW(hWnd, wPath, sec);
540}
541
542/*************************************************************************
543 * SHCreateDirectoryExW [SHELL32.@]
544 */
545DWORD WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
546{
547 DWORD ret = ERROR_BAD_PATHNAME;
548 TRACE("(%p, %s, %p)\n",hWnd, debugstr_w(path), sec);
549
550 if (PathIsRelativeW(path))
551 {
552 SetLastError(ret);
553 }
554 else
555 {
556 ret = SHNotifyCreateDirectoryW(path, sec);
557 if (ret && ret != ERROR_FILE_EXISTS &&
558 ret != ERROR_ALREADY_EXISTS &&
559 ret != ERROR_FILENAME_EXCED_RANGE)
560 {
561 /* handling network file names?
562 lstrcpynW(pathName, path, MAX_PATH);
563 lpStr = PathAddBackslashW(pathName);*/
564 FIXME("Semi-stub, creating path %s, failed with error %ld?\n", debugstr_w(path), ret);
565 }
566 }
567 return ret;
568}
569
570/*************************************************************************
571 * SHFreeNameMappings [shell32.246]
572 *
573 * Free the mapping handle returned by SHFileoperation if FOF_WANTSMAPPINGHANDLE
574 * was specified.
575 *
576 * PARAMS
577 * hNameMapping [I] handle to the name mappings used during renaming of files
578 *
579 */
580void WINAPI SHFreeNameMappings(HANDLE hNameMapping)
581{
582 if (hNameMapping)
583 {
584 LPSHNAMEMAPPINGW lp;
585 UINT i = 0;
586
587 while (NULL != (lp = DSA_GetItemPtr((struct _DSA* const)hNameMapping, i++)))
588 {
589 SHFree(lp->pszOldPath);
590 SHFree(lp->pszNewPath);
591 }
592 DSA_Destroy((struct _DSA*)hNameMapping);
593 }
594}
595
596static BOOL SetIfPointer(LPWSTR pToSlash, WCHAR x)
597{
598 if (pToSlash) *pToSlash = x;
599 return (BOOL)pToSlash;
600}
601/*************************************************************************
602 * SHFindAttrW [internal]
603 *
604 * Get the Attributes for a file or directory. The difference to GetAttributes()
605 * is that this function will also work for paths containing wildcard characters
606 * in its filename.
607
608 * PARAMS
609 * path [I] path of directory or file to check
610 * fileOnly [I] TRUE if only files should be found
611 *
612 * RETURNS
613 * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of
614 * the first file or directory found otherwise
615 */
616static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly)
617{
618 WIN32_FIND_DATAW wfd;
619 BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, wWildcardChars));
620 DWORD dwAttr = INVALID_FILE_ATTRIBUTES;
621 HANDLE hFind = FindFirstFileW(pName, &wfd);
622
623 TRACE("%s %d\n", debugstr_w(pName), fileOnly);
624 if (INVALID_HANDLE_VALUE != hFind)
625 {
626 do
627 {
628 if (b_FileMask && IsAttribDir(wfd.dwFileAttributes))
629 continue;
630 dwAttr = wfd.dwFileAttributes;
631 break;
632 }
633 while (FindNextFileW(hFind, &wfd));
634 FindClose(hFind);
635 }
636 return dwAttr;
637}
638
639/*************************************************************************
640 *
641 * SHFileStrICmp HelperFunction for SHFileOperationW
642 *
643 */
644static BOOL SHFileStrICmpW(LPWSTR p1, LPWSTR p2, LPWSTR p1End, LPWSTR p2End)
645{
646 WCHAR C1 = '\0';
647 WCHAR C2 = '\0';
648 int i_Temp = 0 - (toupperW(p1[0]) == toupperW(p2[0]));
649 int i_len1 = lstrlenW(p1);
650 int i_len2 = lstrlenW(p2);
651
652 if (!i_Temp) return FALSE; /* driveletters are different */
653 if (p1End && (&p1[i_len1] >= p1End) && ('\\' == p1End[0]))
654 {
655 C1 = p1End[0];
656 p1End[0] = '\0';
657 i_len1 = lstrlenW(p1);
658 }
659 if (p2End)
660 {
661 if ((&p2[i_len2] >= p2End) && ('\\' == p2End[0]))
662 {
663 C2 = p2End[0];
664 if (C2)
665 p2End[0] = '\0';
666 }
667 }
668 else
669 {
670 if ((i_len1 <= i_len2) && ('\\' == p2[i_len1]))
671 {
672 C2 = p2[i_len1];
673 if (C2)
674 p2[i_len1] = '\0';
675 }
676 }
677 i_len2 = lstrlenW(p2);
678 if (i_len1 == i_len2)
679 i_Temp = lstrcmpiW(p1,p2);
680 if (C1)
681 p1[i_len1] = C1;
682 if (C2)
683 p2[i_len2] = C2;
684 return !(i_Temp);
685}
686
687/*************************************************************************
688 *
689 * SHFileStrCpyCat HelperFunction for SHFileOperationW
690 *
691 */
692static LPWSTR SHFileStrCpyCatW(LPWSTR pTo, LPCWSTR pFrom, LPCWSTR pCatStr)
693{
694 LPWSTR pToFile = NULL;
695 int i_len;
696 if (pTo)
697 {
698 if (pFrom)
699 lstrcpyW(pTo, pFrom);
700 if (pCatStr)
701 {
702 i_len = lstrlenW(pTo);
703 if ((i_len) && ('\\' != pTo[--i_len]))
704 i_len++;
705 pTo[i_len] = '\\';
706 if ('\\' == pCatStr[0])
707 pCatStr++; \
708 lstrcpyW(&pTo[i_len+1], pCatStr);
709 }
710 pToFile = StrRChrW(pTo,NULL,'\\');
711 /* termination of the new string-group */
712 pTo[(lstrlenW(pTo)) + 1] = '\0';
713 }
714 return pToFile;
715}
716
717/**************************************************************************
718 * SHELL_FileNamesMatch()
719 *
720 * Accepts two \0 delimited lists of the file names. Checks whether number of
721 * files in both lists is the same, and checks also if source-name exists.
722 */
723static BOOL SHELL_FileNamesMatch(LPCWSTR pszFiles1, LPCWSTR pszFiles2, BOOL bOnlySrc)
724{
725 LPWSTR pszTemp;
726
727 TRACE("%s %s %d\n", debugstr_w(pszFiles1), debugstr_w(pszFiles2), bOnlySrc);
728
729 while ((pszFiles1[0] != '\0') &&
730 (bOnlySrc || (pszFiles2[0] != '\0')))
731 {
732 pszTemp = StrChrW(pszFiles1,'\\');
733 /* root (without mask/name) is also not allowed as source, tested in W98 */
734 if (!pszTemp || !pszTemp[1])
735 return FALSE;
736 pszTemp = StrPBrkW(pszFiles1, wWildcardChars);
737 if (pszTemp)
738 {
739 WCHAR szMask [MAX_PATH];
740 pszTemp = StrRChrW(pszFiles1, pszTemp, '\\');
741 if (!pszTemp)
742 return FALSE;
743 lstrcpynW(szMask, pszFiles1, (pszTemp - pszFiles1) + 1);
744 /* we will check the root of the mask as valid dir */
745 if (!IsAttribDir(GetFileAttributesW(&szMask[0])))
746 return FALSE;
747 }
748 else
749 {
750 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(pszFiles1))
751 return FALSE;
752 }
753 pszFiles1 += lstrlenW(pszFiles1) + 1;
754 if (!bOnlySrc)
755 pszFiles2 += lstrlenW(pszFiles2) + 1;
756 }
757 return ((pszFiles1[0] == '\0') && (bOnlySrc || (pszFiles2[0] == '\0')));
758}
759
760/*************************************************************************
761 * SHFileOperationCheck
762 */
763static DWORD SHFileOperationCheck(LPSHFILEOPSTRUCTW lpFileOp, LPCSTR* cFO_Name)
764{
765 FILEOP_FLAGS OFl = (FILEOP_FLAGS)lpFileOp->fFlags;
766 long retCode = NO_ERROR;
767 long FuncSwitch = (lpFileOp->wFunc & FO_MASK);
768
769 /* default no error */
770 if (none == WOsVers) {
771 OSVERSIONINFOA info;
772 info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
773 GetVersionExA(&info);
774 WOsVers = wMe; /* <= wMe, tested with w98se and partialy nt4,w95 */
775 /* this cases must be more different */
776 if ((VER_PLATFORM_WIN32_WINDOWS == info.dwPlatformId) &&
777 (4 == info.dwMajorVersion) && (10 == info.dwMinorVersion)) {
778 WOsVers = w98se;
779 retCodeIsInvalid = 0x75; /* <= W98se, what is with wMe? */
780 }
781 if (VER_PLATFORM_WIN32_NT == info.dwPlatformId)
782 {
783 if (4 > info.dwMajorVersion)
784 {
785 WOsVers = nt351;
786 retCodeIsInvalid = 0x75;
787 }
788 if (4 == info.dwMajorVersion)
789 {
790 WOsVers = nt40;
791 retCodeIsInvalid = 0x75;
792 }
793 if (5 == info.dwMajorVersion)
794 {
795 WOsVers = w2k;
796 if (0 < info.dwMinorVersion)
797 WOsVers = wXp; /* Longhorn also ? */
798 retCodeIsInvalid = 0x4c7; /* >= W2K */
799 }
800 if (5 < info.dwMajorVersion)
801 {
802 WOsVers = wXp; /* Longhorn ? */
803 retCodeIsInvalid = 0x4c7; /* >= W2K */
804 }
805 }
806 TRACE("WOsVers:%d Id: %ld Ver: %ld.%ld.%ld, %s\n\n", WOsVers, info.dwPlatformId,
807 info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber, info.szCSDVersion);
808 }
809 if ((FuncSwitch < FO_MOVE) || (FuncSwitch > FO_RENAME))
810 {
811 FuncSwitch = 0;
812 retCode = ERROR_INVALID_PARAMETER;
813 }
814 cFO_Name[0] = cFO_String [FuncSwitch];
815
816 if (!(~FO_MASK & lpFileOp->wFunc)) /* ? Level == 0 */
817 {
818 lpFileOp->hNameMappings = 0;
819 lpFileOp->fAnyOperationsAborted = FALSE;
820 TRACE("%s: flags (0x%04x) : %s%s%s%s%s%s%s%s%s%s%s%s%s \n",cFO_Name[0], OFl,
821 OFl & FOF_MULTIDESTFILES ? "FOF_MULTIDESTFILES " : "",
822 OFl & FOF_CONFIRMMOUSE ? "FOF_CONFIRMMOUSE " : "",
823 OFl & FOF_SILENT ? "FOF_SILENT " : "",
824 OFl & FOF_RENAMEONCOLLISION ? "FOF_RENAMEONCOLLISION " : "",
825 OFl & FOF_NOCONFIRMATION ? "FOF_NOCONFIRMATION " : "",
826 OFl & FOF_WANTMAPPINGHANDLE ? "FOF_WANTMAPPINGHANDLE " : "",
827 OFl & FOF_ALLOWUNDO ? "FOF_ALLOWUNDO " : "",
828 OFl & FOF_FILESONLY ? "FOF_FILESONLY " : "",
829 OFl & FOF_SIMPLEPROGRESS ? "FOF_SIMPLEPROGRESS " : "",
830 OFl & FOF_NOCONFIRMMKDIR ? "FOF_NOCONFIRMMKDIR " : "",
831 OFl & FOF_NOERRORUI ? "FOF_NOERRORUI " : "",
832 OFl & FOF_NOCOPYSECURITYATTRIBS ? "FOF_NOCOPYSECURITYATTRIBS" : "",
833 OFl & 0xf000 ? "MORE-UNKNOWN-Flags" : "");
834 OFl &= (~(FOF_FILESONLY | FOF_WANTMAPPINGHANDLE | FOF_NOCONFIRMATION |
835 FOF_RENAMEONCOLLISION | FOF_MULTIDESTFILES)); /* implemented */
836 OFl ^= (FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS); /* ignored, if one */
837 OFl &= (~FOF_SIMPLEPROGRESS); /* ignored, only with FOF_SILENT */
838 if (OFl)
839 {
840 if (OFl & (~(FOF_CONFIRMMOUSE | FOF_SILENT |
841 FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS)))
842 {
843 TRACE("%s lpFileOp->fFlags=0x%x not implemented, Aborted=TRUE, stub\n", cFO_Name[0], OFl);
844 retCode = ERROR_INVALID_PARAMETER;
845 }
846 else
847 {
848 TRACE("%s lpFileOp->fFlags=0x%x not fully implemented, stub\n", cFO_Name[0], OFl);
849 } /* endif */
850 } /* endif */
851 }
852 return retCode;
853}
854
855/*************************************************************************
856 *
857 * SHCreateMappingElement internal HelperFunction for SHRenameOnCollision
858 *
859 * Old/New MappingNameElement
860 */
861static LPWSTR SHCreateMappingElement(PINT size, LPWSTR pName)
862{
863 LPWSTR pMapName = NULL;
864 CHAR TempPath [MAX_PATH];
865 int isize = lstrlenW(pName)+1;
866 int oldsize = isize;
867 if ((w98se >= WOsVers) && (nt40 != WOsVers) && (nt351 != WOsVers))
868 {
869 isize = WideCharToMultiByte( CP_ACP, 0, pName, oldsize, TempPath, MAX_PATH, NULL, NULL );
870 if (0 < isize)
871 pName = (LPWSTR)&TempPath[0];
872 }
873 else
874 {
875 oldsize = oldsize*sizeof(WCHAR);
876 }
877 pMapName = SHAlloc(oldsize);
878 memcpy(pMapName,pName,oldsize);
879 *size = isize-1;
880 return pMapName;
881}
882
883/*************************************************************************
884 *
885 * SHRenameOnCollision internal HelperFunction for SHFileOperationW
886 *
887 * Creates new Names and Checks for existance.
888 *
889 * w98 has problems with some remames on collision, if the original target-name,
890 * or the root of the target-name is equal any former orginal target-name,
891 * and if the root-dir of the target differs from the partiell root-dir of the source.
892 * If we have different target-names or has all target the same root of hear source,
893 * we have the problem not, root of target can be shorter as the full root of source.
894 * I think, that is a mapping-problem.
895 * Move within the same or shorter root can made from fo_rename,
896 * that works every in w98, only move/copy in other roots has this problem,
897 * this must be done in fo_move/fo_copy, there we have the problem.
898 * we ignore this problem, it is solved in w2k.
899 */
900static WCHAR wStrFormat[] = {' ','(','%','d',')',' ',0};
901
902static DWORD SHRenameOnCollision(LPWSTR pTempTo, LPWSTR pToFile, LPCWSTR pFromFile, LPSHFILEOPSTRUCTW lpFileOp)
903{
904/* todo ERROR_FILENAME_EXCED_RANGE ?? */
905 static WCHAR wCopy_x_of[40] = {0};
906 LPWSTR pszTemp = wStrFormat + 5;
907 DWORD ToAttr;
908 WCHAR szNumber[16];
909 WCHAR szOldToName [MAX_PATH];
910 int number = 0;
911
912 while (ToAttr = SHFindAttrW(pTempTo, FALSE),
913 (INVALID_FILE_ATTRIBUTES != ToAttr) && (FOF_RENAMEONCOLLISION & lpFileOp->fFlags))
914 {
915 if (!number)
916 {
917 if (!*wCopy_x_of && !LoadStringW(shell32_hInstance, IDS_COPY_X_OF_TEXT, wCopy_x_of, sizeof(wCopy_x_of)-1))
918 break; /* should never be */
919 lstrcpyW(szOldToName, pTempTo);
920 }
921 number++;
922 if (1 < number)
923 {
924 pszTemp = szNumber;
925 wsprintfW(szNumber, wStrFormat, number);
926 }
927 wsprintfW(pToFile + 1,wCopy_x_of, pszTemp, pFromFile + 1);
928 pToFile[lstrlenW(pToFile)+1] = 0;
929
930 } /* endwhile */
931 if (number)
932 {
933 SHNAMEMAPPINGW shm;
934 shm.pszOldPath = SHCreateMappingElement(&shm.cchOldPath, szOldToName);
935 shm.pszNewPath = SHCreateMappingElement(&shm.cchNewPath, pTempTo);
936 if (!lpFileOp->hNameMappings)
937 lpFileOp->hNameMappings = DSA_Create(sizeof(SHNAMEMAPPINGW),1);
938 DSA_InsertItem (lpFileOp->hNameMappings, ((HDSA)lpFileOp->hNameMappings)->nItemCount, &shm);
939 }
940 return ToAttr;
941}
942/*************************************************************************
943 *
944 * SHFileOperationMove HelperFunction for SHFileOperationW
945 *
946 * Contains the common part of FO_MOVE/FO_RENAME.
947 * It is not with recursive call solvable.
948 * We have both tryed FO_RENAME contains not FO_MOVE and
949 * also FO_RENAME does not contains FO_MOVE, only common Parts.
950 */
951static DWORD SHFileOperationMove(LPSHFILEOPSTRUCTW lpFileOp)
952{
953 WCHAR szToName [MAX_PATH + 4];
954 LPCWSTR pTo = lpFileOp->pTo;
955 int i_lenTo;
956 if (NULL != StrPBrkW(pTo, wWildcardChars))
957 {
958 if (w98se >= WOsVers) /* wMe? */
959 return ERROR_FILE_NOT_FOUND; /* w98se */
960 return ERROR_INVALID_NAME; /* w2k */
961 }
962
963 /* FOF_NOCONFIRMATION, FOF_RENAMEONCOLLISION ? */
964 i_lenTo = lstrlenW(pTo);
965 if (i_lenTo && ('\\' == pTo[--i_lenTo]))
966 {
967 lstrcpynW(szToName, pTo, i_lenTo + 1);
968 pTo = &szToName[0];
969 }
970 if (INVALID_FILE_ATTRIBUTES != SHFindAttrW(pTo, FALSE))
971 {
972 return ERROR_ALREADY_EXISTS;
973 }
974 /* we use SHNotifyMoveFile() instead of MoveFileW */
975 return SHNotifyMoveFileW(lpFileOp->pFrom, pTo);
976}
977/*************************************************************************
978 * SHFileOperationW [SHELL32.@]
979 *
980 * See SHFileOperationA
981 */
982DWORD WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
983{
984 SHFILEOPSTRUCTW nFileOp = lpFileOp ? *(lpFileOp):nFileOp;
985
986 LPCWSTR pNextFrom = nFileOp.pFrom;
987 LPCWSTR pNextTo = nFileOp.pTo;
988 LPCWSTR pFrom = pNextFrom;
989 LPCWSTR pTo = NULL;
990 HANDLE hFind = INVALID_HANDLE_VALUE;
991 WIN32_FIND_DATAW wfd;
992 LPWSTR pTempFrom = NULL;
993 LPWSTR pTempTo = NULL;
994 LPWSTR pFromFile;
995 LPWSTR pToFile = NULL;
996 LPWSTR pToTailSlash; /* points also to ToMask, we know not what is this */
997 LPWSTR lpFileName; /* temporary for pFromFile,pToFile,pToTailSlash */
998 DWORD ToAttr;
999 DWORD ToPathAttr;
1000 DWORD FromAttr;
1001
1002 long FuncSwitch = (nFileOp.wFunc & FO_MASK);
1003 long level= nFileOp.wFunc>>FO_LevelShift;
1004
1005 BOOL b_Multi = (0 != (nFileOp.fFlags & FOF_MULTIDESTFILES));
1006
1007 BOOL b_MultiTo = (FO_DELETE != FuncSwitch);
1008 BOOL b_MultiPaired = (0 < level) /* FALSE only at Level 0 */;
1009 BOOL b_MultiFrom = FALSE;
1010 BOOL b_RenamOnColl = (0 != (nFileOp.fFlags & FOF_RENAMEONCOLLISION));
1011 BOOL b_NoConfirm = (0 != (nFileOp.fFlags & FOF_NOCONFIRMATION));
1012 BOOL b_ask_overwrite = (!b_NoConfirm && !b_RenamOnColl);
1013 BOOL b_not_overwrite = (!b_NoConfirm || b_RenamOnColl);
1014 BOOL b_DELETE = (FO_DELETE == FuncSwitch);
1015 BOOL b_SameRoot;
1016 BOOL b_SameRoot_MOVE;
1017 BOOL b_ToMask = FALSE;
1018 BOOL b_FromMask;
1019 BOOL b_FileMask;
1020
1021 LPCSTR cFO_Name;
1022 long retCode = SHFileOperationCheck(&nFileOp,&cFO_Name);
1023
1024 if (retCode)
1025 {
1026 if (('?' == cFO_Name[3]) && (/* wMe ?*/ w98se >= WOsVers))
1027 retCode = NO_ERROR; /* nt4.0, w95, w98se returns no error, w2k, wXp not */
1028 goto shfileop_exit; /* no valid FunctionCode */
1029 }
1030
1031 /* establish when pTo is interpreted as the name of the destination file
1032 * or the directory where the Fromfile should be copied to.
1033 * This depends on:
1034 * (1) pTo points to the name of an existing directory;
1035 * (2) the flag FOF_MULTIDESTFILES is present;
1036 * (3) whether pFrom point to multiple filenames.
1037 *
1038 * Some experiments:
1039 *
1040 * destisdir 1 1 1 1 0 0 0 0
1041 * FOF_MULTIDESTFILES 1 1 0 0 1 1 0 0
1042 * multiple from filenames 1 0 1 0 1 0 1 0
1043 * ---------------
1044 * copy files to dir 1 0 1 1 0 0 1 0
1045 * create dir 0 0 0 0 0 0 1 0
1046 */
1047/*
1048 * FOF_MULTIDESTFILES, FOF_NOCONFIRMATION, FOF_FILESONLY are implemented
1049 * FOF_CONFIRMMOUSE, FOF_SILENT, FOF_NOCONFIRMMKDIR,
1050 * FOF_SIMPLEPROGRESS, FOF_NOCOPYSECURITYATTRIBS are not implemented and ignored
1051 * FOF_RENAMEONCOLLISION are implemented partially and breaks if file exist
1052 * FOF_ALLOWUNDO, FOF_WANTMAPPINGHANDLE are not implemented and breaks
1053 * if any other flag set, an error occurs
1054 */
1055 TRACE(" %s level=%ld nFileOp.fFlags=0x%x\n", cFO_Name, level, nFileOp.fFlags);
1056
1057 if ((pNextFrom) && (!(b_MultiTo) || (pNextTo)))
1058 {
1059 nFileOp.pFrom = pTempFrom = SHAlloc(((1 + 2 * (b_MultiTo)) * MAX_PATH + 6) * sizeof(WCHAR));
1060 if (!pTempFrom)
1061 {
1062 retCode = ERROR_OUTOFMEMORY;
1063 SetLastError(retCode);
1064 goto shfileop_exit;
1065 }
1066 if (b_MultiTo)
1067 pTempTo = &pTempFrom[MAX_PATH + 4];
1068 nFileOp.pTo = pTempTo;
1069 if (0 == level)
1070 {
1071 if ((0 != (nFileOp.fFlags & FOF_NOCONFIRMATION)) && b_RenamOnColl)
1072 nFileOp.fAnyOperationsAborted |= FOI_NeverOverwrite;
1073 }
1074 }
1075 else
1076 {
1077 retCode = 0x402; /* 1026 */
1078 goto shfileop_exit;
1079 }
1080 /* need break at error before change sourcepointer */
1081 while(!retCode && (pNextFrom[0]))
1082 {
1083 nFileOp.wFunc = ((level + 1) << FO_LevelShift) + FuncSwitch;
1084 nFileOp.fFlags = lpFileOp->fFlags;
1085
1086 if (b_MultiTo)
1087 {
1088 pTo = pNextTo;
1089 pNextTo = &pNextTo[lstrlenW(pTo)+1];
1090 b_MultiTo = (b_Multi && (0 != pNextTo[0]));
1091 }
1092
1093 pFrom = pNextFrom;
1094 pNextFrom = &pNextFrom[lstrlenW(pNextFrom) + 1];
1095 if (!b_MultiFrom && !b_MultiTo)
1096 b_MultiFrom = (0 != pNextFrom[0]);
1097
1098 pFromFile = SHFileStrCpyCatW(pTempFrom, pFrom, NULL);
1099 b_FromMask = (pFromFile && (NULL != StrPBrkW(&pFromFile[1], wWildcardChars)));
1100
1101 if (pTo)
1102 {
1103 pToFile = SHFileStrCpyCatW(pTempTo, pTo, NULL);
1104 b_ToMask = (NULL != StrPBrkW(pTempTo, wWildcardChars));
1105 }
1106
1107 if (FO_RENAME == FuncSwitch)
1108 {
1109 if (b_MultiTo || b_MultiFrom || (b_FromMask && !b_ToMask))
1110 {
1111 if (/* wMe? */ w2k <= WOsVers)
1112 retCode = ERROR_GEN_FAILURE; /* W2K ERROR_GEN_FAILURE, w95,W98,NT40 returns no error */
1113 goto shfileop_exit;
1114 }
1115 }
1116
1117 if (!b_MultiPaired)
1118 {
1119 b_MultiPaired =
1120 SHELL_FileNamesMatch(lpFileOp->pFrom,
1121 lpFileOp->pTo, (b_DELETE || !b_Multi || b_MultiFrom));
1122 } /* endif */
1123 if (!(b_MultiPaired) || !(pFromFile) || !(pFromFile[1]) || ((pTo) && !(pToFile)))
1124 {
1125 if (nt40 < WOsVers)
1126 retCode = 0x402; /* 1026, nt40 returns 0 */
1127 goto shfileop_exit;
1128 }
1129
1130 b_FileMask = b_FromMask && (FOF_FILESONLY & nFileOp.fFlags);
1131 if (!b_DELETE)
1132 {
1133 b_SameRoot = (toupperW(pTempFrom[0]) == toupperW(pTempTo[0]));
1134 b_SameRoot_MOVE = (b_SameRoot && (FO_MOVE == FuncSwitch));
1135 }
1136 FromAttr = SHFindAttrW(pTempFrom, b_FileMask);
1137 if (INVALID_FILE_ATTRIBUTES == FromAttr)
1138 {
1139 retCode = GetLastError();
1140 if (ERROR_FILE_NOT_FOUND == retCode || ERROR_NO_MORE_FILES == retCode) /* only tested in wXp,w2k,w98 */
1141 {
1142 if (b_FromMask)
1143 {
1144 retCode = NO_ERROR;
1145 }
1146 else
1147 if (!b_SameRoot_MOVE && !b_DELETE)
1148 goto shfileop_IsInvalid;
1149 }
1150 continue;
1151 } /* endif */
1152
1153 if (b_DELETE)
1154 {
1155 hFind = FindFirstFileW(pFrom, &wfd);
1156 if (INVALID_HANDLE_VALUE == hFind)
1157 {
1158 goto shfileop_IsInvalid;
1159 }
1160 do
1161 {
1162 lpFileName = wfd.cAlternateFileName;
1163 if (!lpFileName[0])
1164 lpFileName = wfd.cFileName;
1165 if (IsDotDir(lpFileName) ||
1166 (b_FileMask && IsAttribDir(wfd.dwFileAttributes)))
1167 continue;
1168 SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL);
1169 /* TODO: Check the SHELL_DeleteFileOrDirectoryW() function in shell32.dll */
1170 if (IsAttribFile(wfd.dwFileAttributes))
1171 {
1172 if (SHNotifyDeleteFileW(pTempFrom))
1173 retCode = 0x78; /* value unknown */
1174 }
1175 else
1176 {
1177 if (!SHELL_DeleteDirectoryW(pTempFrom, (!(nFileOp.fFlags & FOF_NOCONFIRMATION))))
1178 retCode = 0x79; /* value unknown */
1179 }
1180 } while (!retCode && FindNextFileW(hFind, &wfd));
1181 FindClose(hFind);
1182 continue;
1183 } /* FO_DELETE ends, pTo must be always valid from here */
1184
1185 pToTailSlash = NULL;
1186 if ((pToFile) && !(pToFile[1]))
1187 {
1188 pToFile[0] = '\0';
1189 lpFileName = StrRChrW(pTempTo, NULL, '\\');
1190 if (lpFileName)
1191 {
1192 pToTailSlash = pToFile;
1193 pToFile = lpFileName;
1194 }
1195 }
1196 ToPathAttr = ToAttr = GetFileAttributesW(pTempTo);
1197 if (!IsAttribDir(ToAttr) && SetIfPointer(pToFile, '\0'))
1198 {
1199 ToPathAttr = GetFileAttributesW(pTempTo);
1200 *pToFile = '\\';
1201 }
1202 SetIfPointer(pToTailSlash,'\\');
1203
1204 /* FO_RENAME is not only a Filter for FO_MOVE */
1205 if (FO_RENAME == FuncSwitch)
1206 {
1207 if (!(b_FromMask) && (b_SameRoot) && SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL))
1208 {
1209 if (!b_RenamOnColl) /* neu */
1210 /* target is the same as source ? W98 has 0x71, W2K also */
1211 retCode = 0x71;
1212 goto shfileop_exit;
1213 } /* endif */
1214 if ((nt40 == WOsVers) && (b_FromMask))
1215 {
1216 retCode = 0x72; /* only found in nt40, not w98se or w2k */
1217 goto shfileop_exit;
1218 } /* endif */
1219 if ((b_FromMask && !b_ToMask) || !b_SameRoot || \
1220 !SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, NULL) || \
1221 SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, HIGH_ADR))
1222 {
1223 retCode = 0x73; /* must be here or before, not later */
1224 goto shfileop_exit;
1225 }
1226 if (!(b_ToMask) && !(pToTailSlash) && IsAttribDir(ToAttr))
1227 {
1228 if (IsAttribDir(FromAttr))
1229 ToAttr = SHRenameOnCollision(pTempTo, pToFile, pFromFile, &nFileOp);
1230 if ((w98se >= WOsVers) && IsAttribDir(ToAttr /* can be changed from SHRenameOnCollision */))
1231 {
1232 /* never in W2K */
1233 retCode = ERROR_INVALID_NAME; /* FOF_NOCONFIRMATION ? */
1234 goto shfileop_exit;
1235 }
1236 } /* endif */
1237 if ((INVALID_FILE_ATTRIBUTES != ToPathAttr) || (b_ToMask && (w98se >= WOsVers)))
1238 /* w2k only (INVALID_FILE_ATTRIBUTES != ToPathAttr) */
1239 {
1240 retCode = SHFileOperationMove(&nFileOp);
1241 }
1242 else
1243 /* W98 returns 0x75, W2K 0x4c7 */
1244 retCode = retCodeIsInvalid;
1245 goto shfileop_exit;
1246 } /* FO_RENAME ends, only FO_MOVE or FO_COPY valid from here */
1247
1248 if (b_FromMask)
1249 {
1250 /* FO_COPY/FO_MOVE with mask, FO_DELETE are solved long before */
1251 hFind = FindFirstFileW(pFrom, &wfd);
1252 if (INVALID_HANDLE_VALUE == hFind)
1253 {
1254 /* the retcode for this case is unknown */
1255 goto shfileop_IsInvalid;
1256 }
1257 SetIfPointer(pToTailSlash, '\0');
1258 ToAttr = SHFindAttrW(pTempTo, FALSE);
1259 if (b_ToMask && !b_Multi)
1260 nFileOp.fFlags &= ~FOF_RENAMEONCOLLISION;
1261 nFileOp.fFlags &= ~FOF_MULTIDESTFILES;
1262 pToFile = SHFileStrCpyCatW(pTempTo, NULL, wBackslash);
1263 do
1264 {
1265 lpFileName = wfd.cAlternateFileName;
1266 if (!lpFileName[0])
1267 lpFileName = wfd.cFileName;
1268 if (IsDotDir(lpFileName) ||
1269 (b_FileMask && IsAttribDir(wfd.dwFileAttributes)))
1270 continue; /* next name in pTempFrom(dir) */
1271 if (INVALID_FILE_ATTRIBUTES == ToAttr)
1272 {
1273 if (b_MultiTo)
1274 {
1275 retCode = retCodeIsInvalid;
1276 break; /* we need the FindClose */
1277 }
1278 if (b_ToMask)
1279 {
1280 if ((w2k <= WOsVers) && IsAttribDir(wfd.dwFileAttributes)) /* w2k,wXp is tested */
1281 {
1282 if (w2k == WOsVers)
1283 retCode = ERROR_ALREADY_EXISTS; /* no root for target exist, and w2k send this, kind of mad */
1284 else
1285 retCode = ERROR_INVALID_NAME; /* wXp */
1286 nFileOp.fAnyOperationsAborted |= TRUE;
1287 break;
1288 }
1289 }
1290 else
1291 {
1292 nFileOp.fFlags |= FOF_MULTIDESTFILES;
1293 SHFileStrCpyCatW(&pToFile[1], lpFileName, NULL);
1294 }
1295 }
1296 SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL);
1297 retCode = SHFileOperationW (&nFileOp);
1298 } while(!retCode && FindNextFileW(hFind, &wfd));
1299 FindClose(hFind);
1300 if (retCodeIsInvalid == retCode)
1301 goto shfileop_IsInvalid;
1302 continue;
1303 }
1304
1305 if ((INVALID_FILE_ATTRIBUTES != (FromAttr | ToAttr)) && b_RenamOnColl && !(b_ToMask) && !(pToTailSlash))
1306 {
1307 if (b_SameRoot_MOVE && !(b_Multi) && IsAttribDir(FromAttr & ToAttr) && SHFileStrICmpW(pTempFrom, pTempTo, HIGH_ADR, HIGH_ADR))
1308 {
1309 retCode = ERROR_FILENAME_EXCED_RANGE; /* in w98/w2k recursive move, we does this not */
1310 if (w98se >= WOsVers) retCode |= 0x10000; /* unexpected, seen in w98se and nt40sp6 */
1311 goto shfileop_exit;
1312 }
1313 if ( /* i-means indifferent */
1314// /*bad*/ ( b_SameRoot_MOVE && !(b_Multi) && !(b_MultiFrom) && IsAttribDir(ToAttr) && !(SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL)))
1315// /*bad*/ ( b_SameRoot_MOVE && !(b_Multi) && (b_MultiFrom) && IsAttribDir(ToAttr) && !(SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL)))
1316 /*ok*/ ( b_SameRoot_MOVE && (b_Multi) && !(b_MultiFrom) && IsAttribDir(ToAttr) && !(SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL)))
1317// /*bad*/|| ( b_SameRoot_MOVE && (b_Multi) && (b_MultiFrom) && IsAttribDir(ToAttr) && !(SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL)))
1318//
1319// /*bad*/|| (!b_SameRoot_MOVE && !(b_Multi) && !(b_MultiFrom) && IsAttribDir(ToAttr) && IsAttribDir(FromAttr))
1320// /*i*/ || (!b_SameRoot_MOVE && !(b_Multi) && (b_MultiFrom) && IsAttribDir(ToAttr) && IsAttribDir(FromAttr))
1321 /*ok*/ || (!b_SameRoot_MOVE && (b_Multi) && !(b_MultiFrom) && IsAttribDir(ToAttr) && IsAttribDir(FromAttr))
1322// /*i*/ || (!b_SameRoot_MOVE && (b_Multi) && (b_MultiFrom) && IsAttribDir(ToAttr) && IsAttribDir(FromAttr))
1323//
1324 /*ok*/ || ( b_SameRoot_MOVE && IsAttribFile(FromAttr | ToAttr) && !(SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL)))
1325//
1326 /*ok*/ || (!b_SameRoot_MOVE && IsAttribFile(FromAttr | ToAttr))
1327 /*ok*/ || ((FO_COPY == FuncSwitch) && IsAttribDir(FromAttr) && IsAttribFile(ToAttr))
1328 )
1329 {
1330 ToAttr = SHRenameOnCollision(pTempTo, pToFile, pFromFile, &nFileOp);
1331 } /* endif */
1332 } /* endif */
1333 if ((b_SameRoot) && !(b_MultiFrom) && (!b_RenamOnColl || b_SameRoot_MOVE)
1334 && SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL))
1335 {
1336 if (!b_RenamOnColl && (IsAttribFile(FromAttr))) /* neu */
1337 /* target is the same as source ? W98 has 0x71, W2K also */
1338 retCode = 0x71;
1339 goto shfileop_exit;
1340 } /* endif */
1341
1342 /* Analycing for moving SourceName to as TargetName */
1343 if ((b_MultiFrom || !b_Multi) && (
1344 (IsAttribFile(FromAttr) && IsAttribDir(ToAttr))
1345 || (!b_MultiTo && IsAttribDir(ToAttr))
1346 || (!b_MultiTo && IsAttribDir(FromAttr) && b_MultiFrom && !b_SameRoot_MOVE)
1347 ))
1348 {
1349 SHFileStrCpyCatW(pTempTo, NULL, pFromFile);
1350 /* without FOF_MULTIDESTFILES shlfileop will create dir's recursive */
1351 nFileOp.fFlags |= FOF_MULTIDESTFILES;
1352 retCode = SHFileOperationW(&nFileOp);
1353 continue;
1354 }
1355 /* What can we do with one pair and FO_MOVE/FO_COPY ? */
1356 /* w98se, nt40 can create recursive dirs, W2K not! wMe ist not tested */
1357 if (IsAttribDir(ToPathAttr) || !b_SameRoot_MOVE || (w98se >= WOsVers))
1358 if (!b_MultiFrom)
1359 {
1360 if (IsAttribFile(FromAttr))
1361 {
1362 if (b_SameRoot_MOVE &&
1363 /* IsAttribDir(ToPathAttr) && !pToTailSlash && (bug) */
1364 /* windows-bug, MOVE for File also with pToTailSlash, COPY not for this */
1365 (!(IsAttribFile(ToAttr)) || (!(b_ask_overwrite) && b_not_overwrite)) &&
1366 (IsAttribDir(ToPathAttr) || b_ToMask))
1367 {
1368 /* At the same drive, we can move for FO_MOVE, dir to dir is solved later */
1369 retCode = SHFileOperationMove(&nFileOp);
1370 if ((nt40 == WOsVers) && (ERROR_ALREADY_EXISTS == retCode))
1371 retCode = 0x10005;
1372 continue;
1373 } /* endif */
1374 if (IsAttribDir(ToPathAttr) && !pToTailSlash && !IsAttribDir(ToAttr))
1375 {
1376 if (!(FOI_NeverOverwrite & nFileOp.fAnyOperationsAborted))
1377 {
1378 if (!b_not_overwrite)
1379 ToAttr = INVALID_FILE_ATTRIBUTES;
1380 if (IsAttribFile(ToAttr) && b_not_overwrite)
1381 {
1382 if (b_ask_overwrite)
1383 {
1384 /* we must change the dialog in the future for return 3-4 cases,
1385 * 'Yes','No','Yes for all','Never ?' */
1386 if ((INVALID_FILE_ATTRIBUTES != ToAttr) && !SHELL_ConfirmDialogW(ASK_OVERWRITE_FILE, pTempTo))
1387 {
1388 retCode = 0x10008;
1389 nFileOp.fAnyOperationsAborted |= TRUE;
1390 continue;
1391 }
1392 ToAttr = INVALID_FILE_ATTRIBUTES;
1393 } /* endif */
1394 } /* endif */
1395 } /* endif */
1396 if (INVALID_FILE_ATTRIBUTES == ToAttr)
1397 {
1398 if (SHNotifyCopyFileW(pTempFrom, pTempTo, nFileOp.fFlags & FOF_RENAMEONCOLLISION))
1399 {
1400 /* the retcode for this case is unknown */
1401 retCode = 0x10009;
1402 }
1403 if ((FO_COPY == FuncSwitch) || retCode)
1404 continue;
1405 nFileOp.wFunc = ((level+1) << FO_LevelShift) + FO_DELETE;
1406 retCode = SHFileOperationW(&nFileOp);
1407 continue;
1408 }
1409 }
1410 }
1411 if (IsAttribDir(FromAttr))
1412 {
1413 if (INVALID_FILE_ATTRIBUTES == ToAttr)
1414 {
1415 SetIfPointer(pToTailSlash, '\0');
1416 if (w2k <= WOsVers) /* if w2k,wxp not in w98se, nt40sp6 */
1417 {
1418 ToAttr = SHRenameOnCollision(pTempTo, pToFile, pFromFile, &nFileOp);
1419 if (INVALID_FILE_ATTRIBUTES != ToAttr)
1420 {
1421 if (w2k == WOsVers)
1422 retCode = ERROR_ALREADY_EXISTS; /* w2k */
1423 else
1424 retCode = ERROR_INVALID_NAME; /* wXp */
1425 nFileOp.fAnyOperationsAborted |= TRUE;
1426 goto shfileop_exit;
1427 }
1428 lpFileName = &pToFile[lstrlenW(pToFile)];
1429 if (pToTailSlash)
1430 {
1431 pToTailSlash = lpFileName;
1432 lpFileName++;
1433 }
1434 ((DWORD*)lpFileName)[0] = '\0';
1435 retCode = SHCreateDirectoryExW(NULL,pTempTo, NULL);
1436 }
1437 else
1438 { /* only for < w2k, tested with w98se,nt40sp6 */
1439 if (IsAttribDir(ToPathAttr) || !b_SameRoot_MOVE || b_ToMask)
1440 { /* tested with w98se, partially nt40sp6, w2k(wXp). only w98se,nt40sp6 can loop */
1441 lpFileName = NULL;
1442 while ((lpFileName = StrRChrW(pTempTo,lpFileName, '\\'),
1443 INVALID_FILE_ATTRIBUTES == ToAttr) &&
1444 (':' != lpFileName[-1])) /* not begin with root, or end with root */
1445 {
1446 SetIfPointer(lpFileName, '\0');
1447 ToAttr = GetFileAttributesW(pTempTo); /* for continuing */
1448 SetIfPointer(lpFileName--, '\\');
1449 }
1450 while (lpFileName && lpFileName[1] && !retCode)
1451 {
1452 SetIfPointer(lpFileName,'\\');
1453 lpFileName = StrChrW(++lpFileName,'\\');
1454 SetIfPointer(lpFileName,'\0');
1455 retCode = SHCreateDirectoryExW(NULL,pTempTo, NULL);
1456 }
1457 }
1458 }
1459 if (retCode)
1460 {
1461 retCode = (ERROR_PATH_NOT_FOUND | 0x10000); /* nt40,w98se,w2k,wxp */
1462 goto shfileop_exit;
1463 }
1464 ToAttr = GetFileAttributesW(pTempTo);
1465 SetIfPointer(pToTailSlash,'\\');
1466 nFileOp.fFlags &= ~FOF_RENAMEONCOLLISION;
1467 }
1468 if (IsAttribDir(ToAttr))
1469 {
1470 /* both are existing dirs, we (FO_)MOVE/COPY the content */
1471 pToFile = SHFileStrCpyCatW(pTempFrom, NULL, wWildcardFile);
1472 nFileOp.fFlags &= ~FOF_MULTIDESTFILES;
1473 retCode = SHFileOperationW(&nFileOp);
1474 if ((FO_COPY == FuncSwitch) || retCode)
1475 continue;
1476 ((DWORD*)pToFile)[0] = '\0';
1477 nFileOp.wFunc = ((level+1) << FO_LevelShift) + FO_DELETE;
1478 retCode = SHFileOperationW(&nFileOp);
1479 continue;
1480 }
1481 }
1482 } /* end-!b_MultiFrom */
1483
1484shfileop_IsInvalid:
1485 if (!(b_SameRoot_MOVE) && (nt40 < WOsVers))
1486 {
1487 nFileOp.fAnyOperationsAborted |= TRUE;
1488 } /* endif */
1489 /* NT4.0,W98 returns 0x75, W2K,WXP 0x4c7 */
1490 retCode = retCodeIsInvalid;
1491 break;
1492
1493 } /* end-while */
1494
1495shfileop_exit:
1496 if (pTempFrom)
1497 SHFree(pTempFrom);
1498 TRACE("%s level=%ld AnyOpsAborted=%s ret=0x%lx, with %s %s%s\n",
1499 cFO_Name, level, (TRUE & nFileOp.fAnyOperationsAborted) ? "TRUE":"FALSE",
1500 retCode, debugstr_w(pFrom), pTo ? "-> ":"", debugstr_w(pTo));
1501
1502 if (0 == level)
1503 {
1504 nFileOp.fAnyOperationsAborted &= TRUE;
1505 if (!(nFileOp.fFlags & FOF_WANTMAPPINGHANDLE))
1506 {
1507 SHFreeNameMappings((HANDLE)nFileOp.hNameMappings);
1508 nFileOp.hNameMappings = 0;
1509 }
1510 }
1511 lpFileOp->hNameMappings = nFileOp.hNameMappings;
1512 lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
1513 return retCode;
1514}
1515
1516/*************************************************************************
1517 *
1518 * SHNameTranslate HelperFunction for SHFileOperationA
1519 *
1520 * Translates a list of 0 terminated ASCII strings into Unicode. If *wString
1521 * is NULL, only the necessary size of the string is determined and returned,
1522 * otherwise the ASCII strings are copied into it and the buffer is increased
1523 * to point to the location after the final 0 termination char.
1524 */
1525DWORD SHNameTranslate(LPWSTR* wString, LPCWSTR* pWToFrom, BOOL more)
1526{
1527 DWORD size = 0, aSize = 0;
1528 LPCSTR aString = (LPCSTR)*pWToFrom;
1529
1530 if (aString)
1531 {
1532 do
1533 {
1534 size = lstrlenA(aString) + 1;
1535 aSize += size;
1536 aString += size;
1537 } while ((size != 1) && more);
1538 /* The two sizes might be different in the case of multibyte chars */
1539 size = MultiByteToWideChar(CP_ACP, 0, aString, aSize, *wString, 0);
1540 if (*wString) /* only in the second loop */
1541 {
1542 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, size);
1543 *pWToFrom = *wString;
1544 *wString += size;
1545 }
1546 }
1547 return size;
1548}
1549
1550/*************************************************************************
1551 * SHFileOperationA [SHELL32.@]
1552 *
1553 * Function to copy, move, delete and create one or more files with optional
1554 * user prompts.
1555 *
1556 * PARAMS
1557 * lpFileOp [I/O] pointer to a structure containing all the necessary information
1558 *
1559 * NOTES
1560 * exported by name
1561 */
1562DWORD WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
1563{
1564 SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp);
1565 DWORD retCode = 0, size;
1566 LPWSTR ForFree = NULL, /* we change wString in SHNameTranslate and can't use it for freeing */
1567 wString = NULL; /* we change this in SHNameTranslate */
1568
1569 TRACE("\n");
1570 for (;;) /* every loop calculate size, second translate also, if we have storage for this */
1571 {
1572 size = SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */
1573 if (FO_DELETE != (nFileOp.wFunc & FO_MASK))
1574 size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */
1575 if ((nFileOp.fFlags & FOF_SIMPLEPROGRESS))
1576 size += SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */
1577
1578 if (ForFree)
1579 {
1580 retCode = SHFileOperationW(&nFileOp);
1581 SHFree(ForFree); /* we can not use wString, it was changed */
1582 lpFileOp->hNameMappings = nFileOp.hNameMappings;
1583 lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
1584 }
1585 else
1586 {
1587 wString = ForFree = SHAlloc(size * sizeof(WCHAR));
1588 if (ForFree) continue;
1589 retCode = ERROR_OUTOFMEMORY;
1590 SetLastError(retCode);
1591 }
1592 return retCode;
1593 }
1594}
1595
1596/*************************************************************************
1597 * SHFileOperation [SHELL32.@]
1598 *
1599 */
1600DWORD WINAPI SHFileOperationAW(LPVOID lpFileOp)
1601{
1602 if (SHELL_OsIsUnicode())
1603 return SHFileOperationW(lpFileOp);
1604 return SHFileOperationA(lpFileOp);
1605}
1606
1607/*************************************************************************
1608 * SheGetDirW [SHELL32.281]
1609 *
1610 */
1611HRESULT WINAPI SheGetDirW(LPWSTR u, LPWSTR v)
1612{ FIXME("%p %p stub\n",u,v);
1613 return 0;
1614}
1615
1616/*************************************************************************
1617 * SheChangeDirW [SHELL32.274]
1618 *
1619 */
1620HRESULT WINAPI SheChangeDirW(LPWSTR u)
1621{ FIXME("(%s),stub\n",debugstr_w(u));
1622 return 0;
1623}
1624
1625/*************************************************************************
1626 * IsNetDrive [SHELL32.66]
1627 */
1628BOOL WINAPI IsNetDrive(DWORD drive)
1629{
1630 char root[4];
1631 strcpy(root, "A:\\");
1632 root[0] += (char)drive;
1633 return (GetDriveTypeA(root) == DRIVE_REMOTE);
1634}
1635
Note: See TracBrowser for help on using the repository browser.