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 * Copyright 2019-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Lesser General Public 12 * License as published by the Free Software Foundation; either 13 * version 2.1 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Lesser General Public License for more details. 19 * 20 * You should have received a copy of the GNU Lesser General Public 21 * License along with this library; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 23 */ 24 25 #include "precomp.h" 26 27 WINE_DEFAULT_DEBUG_CHANNEL(shell); 28 29 #define IsAttrib(x, y) ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y))) 30 #define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY)) 31 #define IsAttribDir(x) IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY) 32 #define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0)))) 33 34 #define FO_MASK 0xF 35 36 #define NEW_FILENAME_ON_COPY_TRIES 100 37 38 typedef struct 39 { 40 SHFILEOPSTRUCTW *req; 41 DWORD dwYesToAllMask; 42 BOOL bManyItems; 43 BOOL bCancelled; 44 IProgressDialog *progress; 45 ULARGE_INTEGER completedSize; 46 ULARGE_INTEGER totalSize; 47 WCHAR szBuilderString[50]; 48 } FILE_OPERATION; 49 50 #define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026 51 52 typedef struct 53 { 54 DWORD attributes; 55 LPWSTR szDirectory; 56 LPWSTR szFilename; 57 LPWSTR szFullPath; 58 BOOL bFromWildcard; 59 BOOL bFromRelative; 60 BOOL bExists; 61 } FILE_ENTRY; 62 63 typedef struct 64 { 65 FILE_ENTRY *feFiles; 66 DWORD num_alloc; 67 DWORD dwNumFiles; 68 BOOL bAnyFromWildcard; 69 BOOL bAnyDirectories; 70 BOOL bAnyDontExist; 71 } FILE_LIST; 72 73 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec); 74 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path); 75 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path); 76 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir); 77 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists); 78 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly); 79 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo); 80 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo); 81 82 DWORD WINAPI _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *flFrom); 83 static BOOL _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks); 84 85 /* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations 86 */ 87 struct confirm_msg_info 88 { 89 LPWSTR lpszText; 90 LPWSTR lpszCaption; 91 HICON hIcon; 92 BOOL bYesToAll; 93 }; 94 95 /* as some buttons may be hidden and the dialog height may change we may need 96 * to move the controls */ 97 static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow) 98 { 99 HWND hButton = GetDlgItem(hDlg, iId); 100 RECT r; 101 102 if (bShow) 103 { 104 POINT pt; 105 int width; 106 107 GetWindowRect(hButton, &r); 108 width = r.right - r.left; 109 pt.x = r.left; 110 pt.y = r.top; 111 ScreenToClient(hDlg, &pt); 112 MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE); 113 *xPos -= width + 5; 114 } 115 else 116 ShowWindow(hButton, SW_HIDE); 117 } 118 119 /* Note: we paint the text manually and don't use the static control to make 120 * sure the text has the same height as the one computed in WM_INITDIALOG 121 */ 122 static INT_PTR ConfirmMsgBox_Paint(HWND hDlg) 123 { 124 PAINTSTRUCT ps; 125 HFONT hOldFont; 126 RECT r; 127 HDC hdc; 128 129 BeginPaint(hDlg, &ps); 130 hdc = ps.hdc; 131 SetBkMode(hdc, TRANSPARENT); 132 133 GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r); 134 /* this will remap the rect to dialog coords */ 135 MapWindowPoints(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), hDlg, (LPPOINT)&r, 2); 136 hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0)); 137 DrawTextW(hdc, (LPWSTR)GetPropW(hDlg, L"WINE_CONFIRM"), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK); 138 SelectObject(hdc, hOldFont); 139 EndPaint(hDlg, &ps); 140 141 return TRUE; 142 } 143 144 static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam) 145 { 146 struct confirm_msg_info *info = (struct confirm_msg_info *)lParam; 147 INT xPos, yOffset; 148 int width, height; 149 HFONT hOldFont; 150 HDC hdc; 151 RECT r; 152 153 SetWindowTextW(hDlg, info->lpszCaption); 154 ShowWindow(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), SW_HIDE); 155 SetPropW(hDlg, L"WINE_CONFIRM", info->lpszText); 156 SendDlgItemMessageW(hDlg, IDC_YESTOALL_ICON, STM_SETICON, (WPARAM)info->hIcon, 0); 157 158 /* compute the text height and resize the dialog */ 159 GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r); 160 hdc = GetDC(hDlg); 161 yOffset = r.bottom; 162 hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0)); 163 DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT); 164 SelectObject(hdc, hOldFont); 165 yOffset -= r.bottom; 166 yOffset = min(yOffset, 35); /* don't make the dialog too small */ 167 ReleaseDC(hDlg, hdc); 168 169 GetClientRect(hDlg, &r); 170 xPos = r.right - 7; 171 GetWindowRect(hDlg, &r); 172 width = r.right - r.left; 173 height = r.bottom - r.top - yOffset; 174 MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2, 175 (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE); 176 177 confirm_msg_move_button(hDlg, IDCANCEL, &xPos, yOffset, info->bYesToAll); 178 confirm_msg_move_button(hDlg, IDNO, &xPos, yOffset, TRUE); 179 confirm_msg_move_button(hDlg, IDC_YESTOALL, &xPos, yOffset, info->bYesToAll); 180 confirm_msg_move_button(hDlg, IDYES, &xPos, yOffset, TRUE); 181 182 return TRUE; 183 } 184 185 static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 186 { 187 switch (uMsg) 188 { 189 case WM_INITDIALOG: 190 return ConfirmMsgBox_Init(hDlg, lParam); 191 case WM_PAINT: 192 return ConfirmMsgBox_Paint(hDlg); 193 case WM_COMMAND: 194 EndDialog(hDlg, wParam); 195 break; 196 case WM_CLOSE: 197 EndDialog(hDlg, IDCANCEL); 198 break; 199 } 200 return FALSE; 201 } 202 203 int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll) 204 { 205 struct confirm_msg_info info; 206 207 info.lpszText = lpszText; 208 info.lpszCaption = lpszCaption; 209 info.hIcon = hIcon; 210 info.bYesToAll = bYesToAll; 211 return DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_YESTOALL_MSGBOX), hWnd, ConfirmMsgBoxProc, (LPARAM)&info); 212 } 213 214 /* confirmation dialogs content */ 215 typedef struct 216 { 217 HINSTANCE hIconInstance; 218 UINT icon_resource_id; 219 UINT caption_resource_id, text_resource_id; 220 } SHELL_ConfirmIDstruc; 221 222 static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids) 223 { 224 ids->hIconInstance = shell32_hInstance; 225 switch (nKindOfDialog) 226 { 227 case ASK_DELETE_FILE: 228 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 229 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 230 ids->text_resource_id = IDS_DELETEITEM_TEXT; 231 return TRUE; 232 233 case ASK_DELETE_FOLDER: 234 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 235 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION; 236 ids->text_resource_id = IDS_DELETEITEM_TEXT; 237 return TRUE; 238 239 case ASK_DELETE_MULTIPLE_ITEM: 240 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 241 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 242 ids->text_resource_id = IDS_DELETEMULTIPLE_TEXT; 243 return TRUE; 244 245 case ASK_TRASH_FILE: 246 ids->icon_resource_id = IDI_SHELL_TRASH_FILE; 247 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 248 ids->text_resource_id = IDS_TRASHITEM_TEXT; 249 return TRUE; 250 251 case ASK_TRASH_FOLDER: 252 ids->icon_resource_id = IDI_SHELL_TRASH_FILE; 253 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION; 254 ids->text_resource_id = IDS_TRASHFOLDER_TEXT; 255 return TRUE; 256 257 case ASK_TRASH_MULTIPLE_ITEM: 258 ids->icon_resource_id = IDI_SHELL_TRASH_FILE; 259 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 260 ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT; 261 return TRUE; 262 263 case ASK_CANT_TRASH_ITEM: 264 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 265 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 266 ids->text_resource_id = IDS_CANTTRASH_TEXT; 267 return TRUE; 268 269 case ASK_DELETE_SELECTED: 270 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 271 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 272 ids->text_resource_id = IDS_DELETESELECTED_TEXT; 273 return TRUE; 274 275 case ASK_OVERWRITE_FILE: 276 ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2; 277 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION; 278 ids->text_resource_id = IDS_OVERWRITEFILE_TEXT; 279 return TRUE; 280 281 case ASK_OVERWRITE_FOLDER: 282 ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2; 283 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION; 284 ids->text_resource_id = IDS_OVERWRITEFOLDER_TEXT; 285 return TRUE; 286 287 default: 288 FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog); 289 } 290 return FALSE; 291 } 292 293 static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op) 294 { 295 WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256]; 296 SHELL_ConfirmIDstruc ids; 297 DWORD_PTR args[1]; 298 HICON hIcon; 299 int ret; 300 301 assert(nKindOfDialog >= 0 && nKindOfDialog < 32); 302 if (op && (op->dwYesToAllMask & (1 << nKindOfDialog))) 303 return TRUE; 304 305 if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE; 306 307 LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR)); 308 LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR)); 309 310 args[0] = (DWORD_PTR)szDir; 311 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY, 312 szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)args); 313 hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id)); 314 315 ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems); 316 if (op) 317 { 318 if (ret == IDC_YESTOALL) 319 { 320 op->dwYesToAllMask |= (1 << nKindOfDialog); 321 ret = IDYES; 322 } 323 if (ret == IDCANCEL) 324 op->bCancelled = TRUE; 325 if (ret != IDYES) 326 op->req->fAnyOperationsAborted = TRUE; 327 } 328 return ret == IDYES; 329 } 330 331 BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir) 332 { 333 return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL); 334 } 335 336 static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars) 337 { 338 DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0); 339 340 if (len < minChars) 341 len = minChars; 342 343 *wPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 344 if (*wPath) 345 { 346 MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len); 347 return NO_ERROR; 348 } 349 return E_OUTOFMEMORY; 350 } 351 352 static void SHELL32_FreeUnicodeBuf(LPWSTR wPath) 353 { 354 HeapFree(GetProcessHeap(), 0, wPath); 355 } 356 357 EXTERN_C HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status) 358 { 359 FIXME("(%s, %p) stub\n", debugstr_w(path), status); 360 return E_FAIL; 361 } 362 363 /************************************************************************** 364 * SHELL_DeleteDirectory() [internal] 365 * 366 * Asks for confirmation when bShowUI is true and deletes the directory and 367 * all its subdirectories and files if necessary. 368 */ 369 BOOL SHELL_DeleteDirectoryW(FILE_OPERATION *op, LPCWSTR pszDir, BOOL bShowUI) 370 { 371 BOOL ret = TRUE; 372 HANDLE hFind; 373 WIN32_FIND_DATAW wfd; 374 WCHAR szTemp[MAX_PATH]; 375 376 /* Make sure the directory exists before eventually prompting the user */ 377 PathCombineW(szTemp, pszDir, L"*"); 378 hFind = FindFirstFileW(szTemp, &wfd); 379 if (hFind == INVALID_HANDLE_VALUE) 380 return FALSE; 381 382 if (!bShowUI || (ret = SHELL_ConfirmDialogW(op->req->hwnd, ASK_DELETE_FOLDER, pszDir, NULL))) 383 { 384 do 385 { 386 if (IsDotDir(wfd.cFileName)) 387 continue; 388 PathCombineW(szTemp, pszDir, wfd.cFileName); 389 if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes) 390 ret = SHELL_DeleteDirectoryW(op, szTemp, FALSE); 391 else 392 ret = (SHNotifyDeleteFileW(op, szTemp) == ERROR_SUCCESS); 393 394 if (op->progress != NULL) 395 op->bCancelled |= op->progress->HasUserCancelled(); 396 } while (ret && FindNextFileW(hFind, &wfd) && !op->bCancelled); 397 } 398 FindClose(hFind); 399 if (ret) 400 ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS); 401 return ret; 402 } 403 404 /************************************************************************** 405 * Win32CreateDirectory [SHELL32.93] 406 * 407 * Creates a directory. Also triggers a change notify if one exists. 408 * 409 * PARAMS 410 * path [I] path to directory to create 411 * 412 * RETURNS 413 * TRUE if successful, FALSE otherwise 414 */ 415 416 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 417 { 418 TRACE("(%s, %p)\n", debugstr_w(path), sec); 419 420 if (CreateDirectoryW(path, sec)) 421 { 422 SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL); 423 return ERROR_SUCCESS; 424 } 425 return GetLastError(); 426 } 427 428 /**********************************************************************/ 429 430 EXTERN_C BOOL WINAPI Win32CreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 431 { 432 return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS); 433 } 434 435 /************************************************************************ 436 * Win32RemoveDirectory [SHELL32.94] 437 * 438 * Deletes a directory. Also triggers a change notify if one exists. 439 * 440 * PARAMS 441 * path [I] path to directory to delete 442 * 443 * RETURNS 444 * TRUE if successful, FALSE otherwise 445 */ 446 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path) 447 { 448 BOOL ret; 449 TRACE("(%s)\n", debugstr_w(path)); 450 451 ret = RemoveDirectoryW(path); 452 if (!ret) 453 { 454 /* Directory may be write protected */ 455 DWORD dwAttr = GetFileAttributesW(path); 456 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY)) 457 if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY)) 458 ret = RemoveDirectoryW(path); 459 } 460 if (ret) 461 { 462 SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL); 463 return ERROR_SUCCESS; 464 } 465 return GetLastError(); 466 } 467 468 /***********************************************************************/ 469 470 EXTERN_C BOOL WINAPI Win32RemoveDirectoryW(LPCWSTR path) 471 { 472 return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS); 473 } 474 475 static void _SetOperationTitle(FILE_OPERATION *op) { 476 if (op->progress == NULL) 477 return; 478 WCHAR szTitle[50], szPreflight[50]; 479 UINT animation_id = NULL; 480 481 switch (op->req->wFunc) 482 { 483 case FO_COPY: 484 LoadStringW(shell32_hInstance, IDS_FILEOOP_COPYING, szTitle, sizeof(szTitle)/sizeof(WCHAR)); 485 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR)); 486 animation_id = IDA_SHELL_COPY; 487 break; 488 case FO_DELETE: 489 LoadStringW(shell32_hInstance, IDS_FILEOOP_DELETING, szTitle, sizeof(szTitle)/sizeof(WCHAR)); 490 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR)); 491 animation_id = IDA_SHELL_DELETE; 492 break; 493 case FO_MOVE: 494 LoadStringW(shell32_hInstance, IDS_FILEOOP_MOVING, szTitle, sizeof(szTitle)/sizeof(WCHAR)); 495 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR)); 496 animation_id = IDA_SHELL_COPY; 497 break; 498 default: 499 return; 500 } 501 LoadStringW(shell32_hInstance, IDS_FILEOOP_PREFLIGHT, szPreflight, sizeof(szPreflight)/sizeof(WCHAR)); 502 503 op->progress->SetTitle(szTitle); 504 op->progress->SetLine(1, szPreflight, false, NULL); 505 op->progress->SetAnimation(shell32_hInstance, animation_id); 506 } 507 508 static void _SetOperationTexts(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest) { 509 if (op->progress == NULL || src == NULL) 510 return; 511 LPWSTR fileSpecS, pathSpecS, fileSpecD, pathSpecD; 512 WCHAR szFolderS[50], szFolderD[50], szFinalString[260]; 513 514 DWORD_PTR args[2]; 515 516 fileSpecS = (pathSpecS = (LPWSTR) src); 517 fileSpecD = (pathSpecD = (LPWSTR) dest); 518 519 // March across the string to get the file path and it's parent dir. 520 for (LPWSTR ptr = (LPWSTR) src; *ptr; ptr++) { 521 if (*ptr == '\\') { 522 pathSpecS = fileSpecS; 523 fileSpecS = ptr+1; 524 } 525 } 526 lstrcpynW(szFolderS, pathSpecS, min(50, fileSpecS - pathSpecS)); 527 args[0] = (DWORD_PTR) szFolderS; 528 529 switch (op->req->wFunc) 530 { 531 case FO_COPY: 532 case FO_MOVE: 533 if (dest == NULL) 534 return; 535 for (LPWSTR ptr = (LPWSTR) dest; *ptr; ptr++) { 536 if (*ptr == '\\') { 537 pathSpecD = fileSpecD; 538 fileSpecD = ptr + 1; 539 } 540 } 541 lstrcpynW(szFolderD, pathSpecD, min(50, fileSpecD - pathSpecD)); 542 args[1] = (DWORD_PTR) szFolderD; 543 break; 544 case FO_DELETE: 545 break; 546 default: 547 return; 548 } 549 550 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY, 551 op->szBuilderString, 0, 0, szFinalString, sizeof(szFinalString), (va_list*)args); 552 553 op->progress->SetLine(1, fileSpecS, false, NULL); 554 op->progress->SetLine(2, szFinalString, false, NULL); 555 } 556 557 558 DWORD CALLBACK SHCopyProgressRoutine( 559 LARGE_INTEGER TotalFileSize, 560 LARGE_INTEGER TotalBytesTransferred, 561 LARGE_INTEGER StreamSize, 562 LARGE_INTEGER StreamBytesTransferred, 563 DWORD dwStreamNumber, 564 DWORD dwCallbackReason, 565 HANDLE hSourceFile, 566 HANDLE hDestinationFile, 567 LPVOID lpData 568 ) { 569 FILE_OPERATION *op = (FILE_OPERATION *) lpData; 570 571 if (op->progress) { 572 /* 573 * This is called at the start of each file. To keop less state, 574 * I'm adding the file to the completed size here, and the re-subtracting 575 * it when drawing the progress bar. 576 */ 577 if (dwCallbackReason & CALLBACK_STREAM_SWITCH) 578 op->completedSize.QuadPart += TotalFileSize.QuadPart; 579 580 op->progress->SetProgress64(op->completedSize.QuadPart - 581 TotalFileSize.QuadPart + 582 TotalBytesTransferred.QuadPart 583 , op->totalSize.QuadPart); 584 585 586 op->bCancelled = op->progress->HasUserCancelled(); 587 } 588 589 return 0; 590 } 591 592 593 /************************************************************************ 594 * SHNotifyDeleteFileW [internal] 595 * 596 * Deletes a file. Also triggers a change notify if one exists. 597 * 598 * PARAMS 599 * op [I] File Operation context 600 * path [I] path to source file to move 601 * 602 * RETURNS 603 * ERORR_SUCCESS if successful 604 */ 605 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path) 606 { 607 BOOL ret; 608 609 TRACE("(%s)\n", debugstr_w(path)); 610 611 _SetOperationTexts(op, path, NULL); 612 613 LARGE_INTEGER FileSize; 614 FileSize.QuadPart = 0; 615 616 WIN32_FIND_DATAW wfd; 617 HANDLE hFile = FindFirstFileW(path, &wfd); 618 if (hFile != INVALID_HANDLE_VALUE && IsAttribFile(wfd.dwFileAttributes)) { 619 ULARGE_INTEGER tmp; 620 tmp.u.LowPart = wfd.nFileSizeLow; 621 tmp.u.HighPart = wfd.nFileSizeHigh; 622 FileSize.QuadPart = tmp.QuadPart; 623 } 624 FindClose(hFile); 625 626 ret = DeleteFileW(path); 627 if (!ret) 628 { 629 /* File may be write protected or a system file */ 630 DWORD dwAttr = GetFileAttributesW(path); 631 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) 632 if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) 633 ret = DeleteFileW(path); 634 } 635 if (ret) 636 { 637 // Bit of a hack to make the progress bar move. We don't have progress inside the file, so inform when done. 638 SHCopyProgressRoutine(FileSize, FileSize, FileSize, FileSize, 0, CALLBACK_STREAM_SWITCH, NULL, NULL, op); 639 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL); 640 return ERROR_SUCCESS; 641 } 642 return GetLastError(); 643 } 644 645 /************************************************************************ 646 * Win32DeleteFile [SHELL32.164] 647 * 648 * Deletes a file. Also triggers a change notify if one exists. 649 * 650 * PARAMS 651 * path [I] path to file to delete 652 * 653 * RETURNS 654 * TRUE if successful, FALSE otherwise 655 */ 656 EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path) 657 { 658 return (SHNotifyDeleteFileW(NULL, path) == ERROR_SUCCESS); 659 } 660 661 /************************************************************************ 662 * CheckForError [internal] 663 * 664 * Show message box if operation failed 665 * 666 * PARAMS 667 * op [I] File Operation context 668 * error [I] Error code 669 * src [I] Source file full name 670 * 671 */ 672 static DWORD CheckForError(FILE_OPERATION *op, DWORD error, LPCWSTR src) 673 { 674 CStringW strTitle, strMask, strText; 675 LPWSTR lpMsgBuffer; 676 677 if (error == ERROR_SUCCESS || (op->req->fFlags & (FOF_NOERRORUI | FOF_SILENT))) 678 goto exit; 679 680 strTitle.LoadStringW(op->req->wFunc == FO_COPY ? IDS_COPYERRORTITLE : IDS_MOVEERRORTITLE); 681 682 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 683 NULL, 684 error, 685 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 686 (LPWSTR)&lpMsgBuffer, 687 0, 688 NULL); 689 690 strText.Format(op->req->wFunc == FO_COPY ? IDS_COPYERROR : IDS_MOVEERROR, 691 PathFindFileNameW(src), 692 lpMsgBuffer); 693 694 MessageBoxW(op->req->hwnd, strText, strTitle, MB_ICONERROR); 695 LocalFree(lpMsgBuffer); 696 697 exit: 698 return error; 699 } 700 701 /************************************************************************ 702 * SHNotifyMoveFile [internal] 703 * 704 * Moves a file. Also triggers a change notify if one exists. 705 * 706 * PARAMS 707 * op [I] File Operation context 708 * src [I] path to source file to move 709 * dest [I] path to target file to move to 710 * 711 * RETURNS 712 * ERROR_SUCCESS if successful 713 */ 714 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir) 715 { 716 BOOL ret; 717 718 TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest)); 719 720 _SetOperationTexts(op, src, dest); 721 722 ret = MoveFileWithProgressW(src, dest, SHCopyProgressRoutine, op, MOVEFILE_REPLACE_EXISTING); 723 724 /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */ 725 if (!ret) 726 ret = MoveFileW(src, dest); 727 728 if (!ret) 729 { 730 DWORD dwAttr; 731 732 dwAttr = SHFindAttrW(dest, FALSE); 733 if (INVALID_FILE_ATTRIBUTES == dwAttr) 734 { 735 /* Source file may be write protected or a system file */ 736 dwAttr = GetFileAttributesW(src); 737 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) 738 if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) 739 ret = MoveFileW(src, dest); 740 } 741 } 742 if (ret) 743 { 744 SHChangeNotify(isdir ? SHCNE_MKDIR : SHCNE_CREATE, SHCNF_PATHW, dest, NULL); 745 SHChangeNotify(isdir ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_PATHW, src, NULL); 746 return ERROR_SUCCESS; 747 } 748 749 return CheckForError(op, GetLastError(), src); 750 } 751 752 static BOOL SHIsCdRom(LPCWSTR path) 753 { 754 WCHAR tmp[] = { L"A:\\" }; 755 756 if (!path || !path[0]) 757 return FALSE; 758 759 if (path[1] != UNICODE_NULL && path[1] != ':') 760 return FALSE; 761 762 tmp[0] = path[0]; 763 764 return GetDriveTypeW(tmp) == DRIVE_CDROM; 765 } 766 767 /************************************************************************ 768 * SHNotifyCopyFile [internal] 769 * 770 * Copies a file. Also triggers a change notify if one exists. 771 * 772 * PARAMS 773 * src [I] path to source file to move 774 * dest [I] path to target file to move to 775 * bFailIfExists [I] if TRUE, the target file will not be overwritten if 776 * a file with this name already exists 777 * 778 * RETURNS 779 * ERROR_SUCCESS if successful 780 */ 781 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists) 782 { 783 BOOL ret; 784 DWORD attribs; 785 786 TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : ""); 787 788 _SetOperationTexts(op, src, dest); 789 790 /* Destination file may already exist with read only attribute */ 791 attribs = GetFileAttributesW(dest); 792 if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY)) 793 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY); 794 795 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY) 796 { 797 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY); 798 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY) 799 { 800 TRACE("[shell32, SHNotifyCopyFileW] STILL SHIT\n"); 801 } 802 } 803 804 ret = CopyFileExW(src, dest, SHCopyProgressRoutine, op, &op->bCancelled, bFailIfExists); 805 if (ret) 806 { 807 // We are copying from a CD-ROM volume, which is readonly 808 if (SHIsCdRom(src)) 809 { 810 attribs = GetFileAttributesW(dest); 811 attribs &= ~FILE_ATTRIBUTE_READONLY; 812 SetFileAttributesW(dest, attribs); 813 } 814 815 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL); 816 return ERROR_SUCCESS; 817 } 818 819 return CheckForError(op, GetLastError(), src); 820 } 821 822 /************************************************************************* 823 * SHCreateDirectory [SHELL32.165] 824 * 825 * This function creates a file system folder whose fully qualified path is 826 * given by path. If one or more of the intermediate folders do not exist, 827 * they will be created as well. 828 * 829 * PARAMS 830 * hWnd [I] 831 * path [I] path of directory to create 832 * 833 * RETURNS 834 * ERROR_SUCCESS or one of the following values: 835 * ERROR_BAD_PATHNAME if the path is relative 836 * ERROR_FILE_EXISTS when a file with that name exists 837 * ERROR_PATH_NOT_FOUND can't find the path, probably invalid 838 * ERROR_INVALID_NAME if the path contains invalid chars 839 * ERROR_ALREADY_EXISTS when the directory already exists 840 * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process 841 * 842 * NOTES 843 * exported by ordinal 844 * Win9x exports ANSI 845 * WinNT/2000 exports Unicode 846 */ 847 int WINAPI SHCreateDirectory(HWND hWnd, LPCWSTR path) 848 { 849 return SHCreateDirectoryExW(hWnd, path, NULL); 850 } 851 852 /************************************************************************* 853 * SHCreateDirectoryExA [SHELL32.@] 854 * 855 * This function creates a file system folder whose fully qualified path is 856 * given by path. If one or more of the intermediate folders do not exist, 857 * they will be created as well. 858 * 859 * PARAMS 860 * hWnd [I] 861 * path [I] path of directory to create 862 * sec [I] security attributes to use or NULL 863 * 864 * RETURNS 865 * ERROR_SUCCESS or one of the following values: 866 * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative 867 * ERROR_INVALID_NAME if the path contains invalid chars 868 * ERROR_FILE_EXISTS when a file with that name exists 869 * ERROR_ALREADY_EXISTS when the directory already exists 870 * ERROR_FILENAME_EXCED_RANGE if the filename was too long to process 871 * 872 * FIXME: Not implemented yet; 873 * SHCreateDirectoryEx also verifies that the files in the directory will be visible 874 * if the path is a network path to deal with network drivers which might have a limited 875 * but unknown maximum path length. If not: 876 * 877 * If hWnd is set to a valid window handle, a message box is displayed warning 878 * the user that the files may not be accessible. If the user chooses not to 879 * proceed, the function returns ERROR_CANCELLED. 880 * 881 * If hWnd is set to NULL, no user interface is displayed and the function 882 * returns ERROR_CANCELLED. 883 */ 884 int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec) 885 { 886 LPWSTR wPath; 887 DWORD retCode; 888 889 TRACE("(%s, %p)\n", debugstr_a(path), sec); 890 891 retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); 892 if (!retCode) 893 { 894 retCode = SHCreateDirectoryExW(hWnd, wPath, sec); 895 SHELL32_FreeUnicodeBuf(wPath); 896 } 897 return retCode; 898 } 899 900 /************************************************************************* 901 * SHCreateDirectoryExW [SHELL32.@] 902 * 903 * See SHCreateDirectoryExA. 904 */ 905 int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 906 { 907 int ret = ERROR_BAD_PATHNAME; 908 TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec); 909 910 if (PathIsRelativeW(path)) 911 { 912 SetLastError(ret); 913 } 914 else 915 { 916 ret = SHNotifyCreateDirectoryW(path, sec); 917 /* Refuse to work on certain error codes before trying to create directories recursively */ 918 if (ret != ERROR_SUCCESS && 919 ret != ERROR_FILE_EXISTS && 920 ret != ERROR_ALREADY_EXISTS && 921 ret != ERROR_FILENAME_EXCED_RANGE) 922 { 923 WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; /* extra for PathAddBackslash() */ 924 925 lstrcpynW(szTemp, path, MAX_PATH); 926 pEnd = PathAddBackslashW(szTemp); 927 pSlash = szTemp + 3; 928 929 while (*pSlash) 930 { 931 while (*pSlash && *pSlash != '\\') pSlash++; 932 if (*pSlash) 933 { 934 *pSlash = 0; /* terminate path at separator */ 935 936 ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL); 937 } 938 *pSlash++ = '\\'; /* put the separator back */ 939 } 940 } 941 942 if (ret && hWnd && (ERROR_CANCELLED != ret && ERROR_ALREADY_EXISTS != ret)) 943 { 944 ShellMessageBoxW(shell32_hInstance, hWnd, MAKEINTRESOURCEW(IDS_CREATEFOLDER_DENIED), MAKEINTRESOURCEW(IDS_CREATEFOLDER_CAPTION), 945 MB_ICONEXCLAMATION | MB_OK, path); 946 ret = ERROR_CANCELLED; 947 } 948 } 949 950 return ret; 951 } 952 953 /************************************************************************* 954 * SHFindAttrW [internal] 955 * 956 * Get the Attributes for a file or directory. The difference to GetAttributes() 957 * is that this function will also work for paths containing wildcard characters 958 * in its filename. 959 960 * PARAMS 961 * path [I] path of directory or file to check 962 * fileOnly [I] TRUE if only files should be found 963 * 964 * RETURNS 965 * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of 966 * the first file or directory found otherwise 967 */ 968 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly) 969 { 970 WIN32_FIND_DATAW wfd; 971 BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, L"*?")); 972 DWORD dwAttr = INVALID_FILE_ATTRIBUTES; 973 HANDLE hFind = FindFirstFileW(pName, &wfd); 974 975 TRACE("%s %d\n", debugstr_w(pName), fileOnly); 976 if (INVALID_HANDLE_VALUE != hFind) 977 { 978 do 979 { 980 if (b_FileMask && IsAttribDir(wfd.dwFileAttributes)) 981 continue; 982 dwAttr = wfd.dwFileAttributes; 983 break; 984 } while (FindNextFileW(hFind, &wfd)); 985 986 FindClose(hFind); 987 } 988 return dwAttr; 989 } 990 991 /************************************************************************* 992 * 993 * _ConvertAtoW helper function for SHFileOperationA 994 * 995 * Converts a string or string-list to unicode. 996 */ 997 static DWORD _ConvertAtoW(PCSTR strSrc, PCWSTR* pStrDest, BOOL isList) 998 { 999 *pStrDest = NULL; 1000 1001 // If the input is null, nothing to convert. 1002 if (!strSrc) 1003 return 0; 1004 1005 // Measure the total size, depending on if it's a zero-terminated list. 1006 int sizeA = 0; 1007 if (isList) 1008 { 1009 PCSTR tmpSrc = strSrc; 1010 int size; 1011 do 1012 { 1013 size = lstrlenA(tmpSrc) + 1; 1014 sizeA += size; 1015 tmpSrc += size; 1016 } while (size != 1); 1017 } 1018 else 1019 { 1020 sizeA = lstrlenA(strSrc) + 1; 1021 } 1022 1023 // Measure the needed allocation size. 1024 int sizeW = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, NULL, 0); 1025 if (!sizeW) 1026 return GetLastError(); 1027 1028 PWSTR strDest = (PWSTR) HeapAlloc(GetProcessHeap(), 0, sizeW * sizeof(WCHAR)); 1029 if (!strDest) 1030 return ERROR_OUTOFMEMORY; 1031 1032 int err = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, strDest, sizeW); 1033 if (!err) 1034 { 1035 HeapFree(GetProcessHeap(), 0, strDest); 1036 return GetLastError(); 1037 } 1038 1039 *pStrDest = strDest; 1040 return 0; 1041 } 1042 1043 /************************************************************************* 1044 * SHFileOperationA [SHELL32.@] 1045 * 1046 * Function to copy, move, delete and create one or more files with optional 1047 * user prompts. 1048 * 1049 * PARAMS 1050 * lpFileOp [I/O] pointer to a structure containing all the necessary information 1051 * 1052 * RETURNS 1053 * Success: ERROR_SUCCESS. 1054 * Failure: ERROR_CANCELLED. 1055 * 1056 * NOTES 1057 * exported by name 1058 */ 1059 int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp) 1060 { 1061 int errCode, retCode; 1062 SHFILEOPSTRUCTW nFileOp = { 0 }; 1063 1064 // Convert A information to W 1065 nFileOp.hwnd = lpFileOp->hwnd; 1066 nFileOp.wFunc = lpFileOp->wFunc; 1067 nFileOp.fFlags = lpFileOp->fFlags; 1068 1069 errCode = _ConvertAtoW(lpFileOp->pFrom, &nFileOp.pFrom, TRUE); 1070 if (errCode != 0) 1071 goto cleanup; 1072 1073 if (FO_DELETE != (nFileOp.wFunc & FO_MASK)) 1074 { 1075 errCode = _ConvertAtoW(lpFileOp->pTo, &nFileOp.pTo, TRUE); 1076 if (errCode != 0) 1077 goto cleanup; 1078 } 1079 1080 if (nFileOp.fFlags & FOF_SIMPLEPROGRESS) 1081 { 1082 errCode = _ConvertAtoW(lpFileOp->lpszProgressTitle, &nFileOp.lpszProgressTitle, FALSE); 1083 if (errCode != 0) 1084 goto cleanup; 1085 } 1086 1087 // Call the actual function 1088 retCode = SHFileOperationW(&nFileOp); 1089 if (retCode) 1090 { 1091 ERR("SHFileOperationW failed with 0x%x\n", retCode); 1092 } 1093 1094 // Cleanup 1095 cleanup: 1096 if (nFileOp.pFrom) 1097 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pFrom); 1098 if (nFileOp.pTo) 1099 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pTo); 1100 if (nFileOp.lpszProgressTitle) 1101 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.lpszProgressTitle); 1102 1103 if (errCode != 0) 1104 { 1105 lpFileOp->fAnyOperationsAborted = TRUE; 1106 SetLastError(errCode); 1107 1108 return errCode; 1109 } 1110 1111 // Thankfully, starting with NT4 the name mappings are always unicode, so no need to convert. 1112 lpFileOp->hNameMappings = nFileOp.hNameMappings; 1113 lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted; 1114 return retCode; 1115 } 1116 1117 static void __inline grow_list(FILE_LIST *list) 1118 { 1119 FILE_ENTRY *newx = (FILE_ENTRY *)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles, 1120 list->num_alloc * 2 * sizeof(*newx) ); 1121 list->feFiles = newx; 1122 list->num_alloc *= 2; 1123 } 1124 1125 /* adds a file to the FILE_ENTRY struct 1126 */ 1127 static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile) 1128 { 1129 DWORD dwLen = lstrlenW(szFile) + 1; 1130 LPCWSTR ptr; 1131 LPCWSTR ptr2; 1132 1133 feFile->szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1134 lstrcpyW(feFile->szFullPath, szFile); 1135 1136 ptr = StrRChrW(szFile, NULL, '\\'); 1137 ptr2 = StrRChrW(szFile, NULL, '/'); 1138 if (!ptr || ptr < ptr2) 1139 ptr = ptr2; 1140 if (ptr) 1141 { 1142 dwLen = ptr - szFile + 1; 1143 feFile->szDirectory = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1144 lstrcpynW(feFile->szDirectory, szFile, dwLen); 1145 1146 dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1; 1147 feFile->szFilename = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1148 lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */ 1149 } 1150 feFile->bFromWildcard = FALSE; 1151 } 1152 1153 static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName) 1154 { 1155 LPCWSTR ptr; 1156 LPCWSTR ptr2; 1157 LPWSTR szFullPath; 1158 DWORD dwDirLen, dwFullLen; 1159 1160 ptr = StrRChrW(szWildCard, NULL, '\\'); 1161 ptr2 = StrRChrW(szWildCard, NULL, '/'); 1162 if (!ptr || ptr < ptr2) 1163 ptr = ptr2; 1164 dwDirLen = ptr - szWildCard + 1; 1165 1166 dwFullLen = dwDirLen + lstrlenW(szFileName) + 1; 1167 szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR)); 1168 1169 lstrcpynW(szFullPath, szWildCard, dwDirLen + 1); 1170 lstrcatW(szFullPath, szFileName); 1171 1172 return szFullPath; 1173 } 1174 1175 static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex) 1176 { 1177 WIN32_FIND_DATAW wfd; 1178 HANDLE hFile = FindFirstFileW(szFile, &wfd); 1179 FILE_ENTRY *file; 1180 LPWSTR szFullPath; 1181 BOOL res; 1182 1183 if (hFile == INVALID_HANDLE_VALUE) return; 1184 1185 for (res = TRUE; res; res = FindNextFileW(hFile, &wfd)) 1186 { 1187 if (IsDotDir(wfd.cFileName)) 1188 continue; 1189 1190 if (*pdwListIndex >= flList->num_alloc) 1191 grow_list( flList ); 1192 1193 szFullPath = wildcard_to_file(szFile, wfd.cFileName); 1194 file = &flList->feFiles[(*pdwListIndex)++]; 1195 add_file_to_entry(file, szFullPath); 1196 file->bFromWildcard = TRUE; 1197 file->attributes = wfd.dwFileAttributes; 1198 1199 if (IsAttribDir(file->attributes)) 1200 flList->bAnyDirectories = TRUE; 1201 1202 HeapFree(GetProcessHeap(), 0, szFullPath); 1203 } 1204 1205 FindClose(hFile); 1206 } 1207 1208 /* takes the null-separated file list and fills out the FILE_LIST */ 1209 static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles) 1210 { 1211 LPCWSTR ptr = szFiles; 1212 WCHAR szCurFile[MAX_PATH]; 1213 DWORD i = 0; 1214 1215 if (!szFiles) 1216 return ERROR_INVALID_PARAMETER; 1217 1218 flList->bAnyFromWildcard = FALSE; 1219 flList->bAnyDirectories = FALSE; 1220 flList->bAnyDontExist = FALSE; 1221 flList->num_alloc = 32; 1222 flList->dwNumFiles = 0; 1223 1224 /* empty list */ 1225 if (!szFiles[0]) 1226 return ERROR_ACCESS_DENIED; 1227 1228 flList->feFiles = (FILE_ENTRY *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1229 flList->num_alloc * sizeof(FILE_ENTRY)); 1230 1231 while (*ptr) 1232 { 1233 if (i >= flList->num_alloc) grow_list( flList ); 1234 1235 /* change relative to absolute path */ 1236 if (PathIsRelativeW(ptr)) 1237 { 1238 GetCurrentDirectoryW(MAX_PATH, szCurFile); 1239 PathCombineW(szCurFile, szCurFile, ptr); 1240 flList->feFiles[i].bFromRelative = TRUE; 1241 } 1242 else 1243 { 1244 lstrcpyW(szCurFile, ptr); 1245 flList->feFiles[i].bFromRelative = FALSE; 1246 } 1247 1248 /* parse wildcard files if they are in the filename */ 1249 if (StrPBrkW(szCurFile, L"*?")) 1250 { 1251 parse_wildcard_files(flList, szCurFile, &i); 1252 flList->bAnyFromWildcard = TRUE; 1253 i--; 1254 } 1255 else 1256 { 1257 FILE_ENTRY *file = &flList->feFiles[i]; 1258 add_file_to_entry(file, szCurFile); 1259 file->attributes = GetFileAttributesW( file->szFullPath ); 1260 file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES); 1261 1262 if (!file->bExists) 1263 flList->bAnyDontExist = TRUE; 1264 1265 if (IsAttribDir(file->attributes)) 1266 flList->bAnyDirectories = TRUE; 1267 } 1268 1269 /* advance to the next string */ 1270 ptr += lstrlenW(ptr) + 1; 1271 i++; 1272 } 1273 flList->dwNumFiles = i; 1274 1275 return S_OK; 1276 } 1277 1278 /* free the FILE_LIST */ 1279 static void destroy_file_list(FILE_LIST *flList) 1280 { 1281 DWORD i; 1282 1283 if (!flList || !flList->feFiles) 1284 return; 1285 1286 for (i = 0; i < flList->dwNumFiles; i++) 1287 { 1288 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory); 1289 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename); 1290 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath); 1291 } 1292 1293 HeapFree(GetProcessHeap(), 0, flList->feFiles); 1294 } 1295 1296 static CStringW try_find_new_name(LPCWSTR szDestPath) 1297 { 1298 CStringW mask(szDestPath); 1299 CStringW ext(PathFindExtensionW(szDestPath)); 1300 1301 // cut off extension before inserting a "new file" mask 1302 if (!ext.IsEmpty()) 1303 { 1304 mask = mask.Left(mask.GetLength() - ext.GetLength()); 1305 } 1306 mask += L" (%d)" + ext; 1307 1308 CStringW newName; 1309 1310 // trying to find new file name 1311 for (int i = 1; i < NEW_FILENAME_ON_COPY_TRIES; i++) 1312 { 1313 newName.Format(mask, i); 1314 1315 if (!PathFileExistsW(newName)) 1316 { 1317 return newName; 1318 } 1319 } 1320 1321 return CStringW(); 1322 } 1323 1324 static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) 1325 { 1326 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1327 FILE_LIST flFromNew, flToNew; 1328 1329 if (feFrom->szFilename && IsDotDir(feFrom->szFilename)) 1330 return; 1331 1332 if (PathFileExistsW(szDestPath)) 1333 PathCombineW(szTo, szDestPath, feFrom->szFilename); 1334 else 1335 lstrcpyW(szTo, szDestPath); 1336 1337 if (PathFileExistsW(szTo)) 1338 { 1339 if (op->req->fFlags & FOF_RENAMEONCOLLISION) 1340 { 1341 CStringW newPath = try_find_new_name(szTo); 1342 if (!newPath.IsEmpty()) 1343 { 1344 StringCchCopyW(szTo, _countof(szTo), newPath); 1345 } 1346 } 1347 else if (!(op->req->fFlags & FOF_NOCONFIRMATION)) 1348 { 1349 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op)) 1350 { 1351 /* Vista returns an ERROR_CANCELLED even if user pressed "No" */ 1352 if (!op->bManyItems) 1353 op->bCancelled = TRUE; 1354 return; 1355 } 1356 } 1357 } 1358 1359 szTo[lstrlenW(szTo) + 1] = '\0'; 1360 SHNotifyCreateDirectoryW(szTo, NULL); 1361 1362 PathCombineW(szFrom, feFrom->szFullPath, L"*.*"); 1363 szFrom[lstrlenW(szFrom) + 1] = '\0'; 1364 1365 ZeroMemory(&flFromNew, sizeof(FILE_LIST)); 1366 ZeroMemory(&flToNew, sizeof(FILE_LIST)); 1367 parse_file_list(&flFromNew, szFrom); 1368 parse_file_list(&flToNew, szTo); 1369 1370 copy_files(op, FALSE, &flFromNew, &flToNew); 1371 1372 destroy_file_list(&flFromNew); 1373 destroy_file_list(&flToNew); 1374 } 1375 1376 static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo) 1377 { 1378 if (PathFileExistsW(szTo)) 1379 { 1380 if (op->req->fFlags & FOF_RENAMEONCOLLISION) 1381 { 1382 CStringW newPath = try_find_new_name(szTo); 1383 if (!newPath.IsEmpty()) 1384 { 1385 return SHNotifyCopyFileW(op, szFrom, newPath, FALSE) == 0; 1386 } 1387 } 1388 else if (!(op->req->fFlags & FOF_NOCONFIRMATION)) 1389 { 1390 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op)) 1391 return FALSE; 1392 } 1393 } 1394 1395 return SHNotifyCopyFileW(op, szFrom, szTo, FALSE) == 0; 1396 } 1397 1398 /* copy a file or directory to another directory */ 1399 static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) 1400 { 1401 if (!PathFileExistsW(feTo->szFullPath)) 1402 SHNotifyCreateDirectoryW(feTo->szFullPath, NULL); 1403 1404 if (IsAttribFile(feFrom->attributes)) 1405 { 1406 WCHAR szDestPath[MAX_PATH]; 1407 1408 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); 1409 copy_file_to_file(op, feFrom->szFullPath, szDestPath); 1410 } 1411 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) 1412 copy_dir_to_dir(op, feFrom, feTo->szFullPath); 1413 } 1414 1415 static void create_dest_dirs(LPCWSTR szDestDir) 1416 { 1417 WCHAR dir[MAX_PATH]; 1418 LPCWSTR ptr = StrChrW(szDestDir, '\\'); 1419 1420 /* make sure all directories up to last one are created */ 1421 while (ptr && (ptr = StrChrW(ptr + 1, '\\'))) 1422 { 1423 lstrcpynW(dir, szDestDir, ptr - szDestDir + 1); 1424 1425 if (!PathFileExistsW(dir)) 1426 SHNotifyCreateDirectoryW(dir, NULL); 1427 } 1428 1429 /* create last directory */ 1430 if (!PathFileExistsW(szDestDir)) 1431 SHNotifyCreateDirectoryW(szDestDir, NULL); 1432 } 1433 1434 /* the FO_COPY operation */ 1435 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo) 1436 { 1437 DWORD i; 1438 const FILE_ENTRY *entryToCopy; 1439 const FILE_ENTRY *fileDest = &flTo->feFiles[0]; 1440 1441 if (flFrom->bAnyDontExist) 1442 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1443 1444 if (flTo->dwNumFiles == 0) 1445 { 1446 /* If the destination is empty, SHFileOperation should use the current directory */ 1447 WCHAR curdir[MAX_PATH+1]; 1448 1449 GetCurrentDirectoryW(MAX_PATH, curdir); 1450 curdir[lstrlenW(curdir)+1] = 0; 1451 1452 destroy_file_list(flTo); 1453 ZeroMemory(flTo, sizeof(FILE_LIST)); 1454 parse_file_list(flTo, curdir); 1455 fileDest = &flTo->feFiles[0]; 1456 } 1457 1458 if (multiDest) 1459 { 1460 if (flFrom->bAnyFromWildcard) 1461 return ERROR_CANCELLED; 1462 1463 if (flFrom->dwNumFiles != flTo->dwNumFiles) 1464 { 1465 if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) 1466 return ERROR_CANCELLED; 1467 1468 /* Free all but the first entry. */ 1469 for (i = 1; i < flTo->dwNumFiles; i++) 1470 { 1471 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory); 1472 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename); 1473 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath); 1474 } 1475 1476 flTo->dwNumFiles = 1; 1477 } 1478 else if (IsAttribDir(fileDest->attributes)) 1479 { 1480 for (i = 1; i < flTo->dwNumFiles; i++) 1481 if (!IsAttribDir(flTo->feFiles[i].attributes) || 1482 !IsAttribDir(flFrom->feFiles[i].attributes)) 1483 { 1484 return ERROR_CANCELLED; 1485 } 1486 } 1487 } 1488 else if (flFrom->dwNumFiles != 1) 1489 { 1490 if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) 1491 return ERROR_CANCELLED; 1492 1493 if (PathFileExistsW(fileDest->szFullPath) && 1494 IsAttribFile(fileDest->attributes)) 1495 { 1496 return ERROR_CANCELLED; 1497 } 1498 1499 if (flTo->dwNumFiles == 1 && fileDest->bFromRelative && 1500 !PathFileExistsW(fileDest->szFullPath)) 1501 { 1502 return ERROR_CANCELLED; 1503 } 1504 } 1505 1506 for (i = 0; i < flFrom->dwNumFiles; i++) 1507 { 1508 entryToCopy = &flFrom->feFiles[i]; 1509 1510 if ((multiDest) && 1511 flTo->dwNumFiles > 1) 1512 { 1513 fileDest = &flTo->feFiles[i]; 1514 } 1515 1516 if (IsAttribDir(entryToCopy->attributes) && 1517 !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory)) 1518 { 1519 return ERROR_SUCCESS; 1520 } 1521 1522 create_dest_dirs(fileDest->szDirectory); 1523 1524 if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath)) 1525 { 1526 if (IsAttribFile(entryToCopy->attributes)) 1527 return ERROR_NO_MORE_SEARCH_HANDLES; 1528 else 1529 return ERROR_SUCCESS; 1530 } 1531 1532 if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) || 1533 IsAttribDir(fileDest->attributes)) 1534 { 1535 copy_to_dir(op, entryToCopy, fileDest); 1536 } 1537 else if (IsAttribDir(entryToCopy->attributes)) 1538 { 1539 copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath); 1540 } 1541 else 1542 { 1543 if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath)) 1544 { 1545 op->req->fAnyOperationsAborted = TRUE; 1546 return ERROR_CANCELLED; 1547 } 1548 } 1549 1550 if (op->progress != NULL) 1551 op->bCancelled |= op->progress->HasUserCancelled(); 1552 /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */ 1553 if (op->bCancelled) 1554 return ERROR_CANCELLED; 1555 } 1556 1557 /* Vista return code. On XP if the used pressed "No" for the last item, 1558 * ERROR_ARENA_TRASHED would be returned */ 1559 return ERROR_SUCCESS; 1560 } 1561 1562 static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom) 1563 { 1564 if (flFrom->dwNumFiles > 1) 1565 { 1566 WCHAR tmp[8]; 1567 1568 wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), L"%d", flFrom->dwNumFiles); 1569 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL); 1570 } 1571 else 1572 { 1573 const FILE_ENTRY *fileEntry = &flFrom->feFiles[0]; 1574 1575 if (IsAttribFile(fileEntry->attributes)) 1576 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL); 1577 else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) 1578 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL); 1579 } 1580 return TRUE; 1581 } 1582 1583 /* the FO_DELETE operation */ 1584 static HRESULT delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom) 1585 { 1586 const FILE_ENTRY *fileEntry; 1587 DWORD i; 1588 BOOL bPathExists; 1589 BOOL bTrash; 1590 1591 if (!flFrom->dwNumFiles) 1592 return ERROR_SUCCESS; 1593 1594 /* Windows also checks only the first item */ 1595 bTrash = (op->req->fFlags & FOF_ALLOWUNDO) 1596 && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath); 1597 1598 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (!bTrash && op->req->fFlags & FOF_WANTNUKEWARNING)) 1599 if (!confirm_delete_list(op->req->hwnd, op->req->fFlags, bTrash, flFrom)) 1600 { 1601 op->req->fAnyOperationsAborted = TRUE; 1602 return 0; 1603 } 1604 1605 /* Check files. Do not delete one if one file does not exists */ 1606 for (i = 0; i < flFrom->dwNumFiles; i++) 1607 { 1608 fileEntry = &flFrom->feFiles[i]; 1609 1610 if (fileEntry->attributes == (ULONG)-1) 1611 { 1612 // This is a windows 2003 server specific value which has been removed. 1613 // Later versions of windows return ERROR_FILE_NOT_FOUND. 1614 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1615 } 1616 } 1617 1618 for (i = 0; i < flFrom->dwNumFiles; i++) 1619 { 1620 bPathExists = TRUE; 1621 fileEntry = &flFrom->feFiles[i]; 1622 1623 if (!IsAttribFile(fileEntry->attributes) && 1624 (op->req->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) 1625 continue; 1626 1627 if (bTrash) 1628 { 1629 BOOL bDelete; 1630 if (TRASH_TrashFile(fileEntry->szFullPath)) 1631 { 1632 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL); 1633 continue; 1634 } 1635 1636 /* Note: Windows silently deletes the file in such a situation, we show a dialog */ 1637 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (op->req->fFlags & FOF_WANTNUKEWARNING)) 1638 bDelete = SHELL_ConfirmDialogW(op->req->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL); 1639 else 1640 bDelete = TRUE; 1641 1642 if (!bDelete) 1643 { 1644 op->req->fAnyOperationsAborted = TRUE; 1645 break; 1646 } 1647 } 1648 1649 /* delete the file or directory */ 1650 if (IsAttribFile(fileEntry->attributes)) 1651 { 1652 bPathExists = (ERROR_SUCCESS == SHNotifyDeleteFileW(op, fileEntry->szFullPath)); 1653 } 1654 else 1655 bPathExists = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE); 1656 1657 if (!bPathExists) 1658 { 1659 DWORD err = GetLastError(); 1660 1661 if (ERROR_FILE_NOT_FOUND == err) 1662 { 1663 // This is a windows 2003 server specific value which ahs been removed. 1664 // Later versions of windows return ERROR_FILE_NOT_FOUND. 1665 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1666 } 1667 else 1668 { 1669 return err; 1670 } 1671 } 1672 1673 if (op->progress != NULL) 1674 op->bCancelled |= op->progress->HasUserCancelled(); 1675 /* Should fire on progress dialog only */ 1676 if (op->bCancelled) 1677 return ERROR_CANCELLED; 1678 } 1679 1680 return ERROR_SUCCESS; 1681 } 1682 1683 static void move_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) 1684 { 1685 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1686 FILE_LIST flFromNew, flToNew; 1687 1688 if (feFrom->szFilename && IsDotDir(feFrom->szFilename)) 1689 return; 1690 1691 SHNotifyCreateDirectoryW(szDestPath, NULL); 1692 1693 PathCombineW(szFrom, feFrom->szFullPath, L"*.*"); 1694 szFrom[lstrlenW(szFrom) + 1] = '\0'; 1695 1696 lstrcpyW(szTo, szDestPath); 1697 szTo[lstrlenW(szDestPath) + 1] = '\0'; 1698 1699 ZeroMemory(&flFromNew, sizeof(FILE_LIST)); 1700 ZeroMemory(&flToNew, sizeof(FILE_LIST)); 1701 parse_file_list(&flFromNew, szFrom); 1702 parse_file_list(&flToNew, szTo); 1703 1704 move_files(op, FALSE, &flFromNew, &flToNew); 1705 1706 destroy_file_list(&flFromNew); 1707 destroy_file_list(&flToNew); 1708 1709 if (PathIsDirectoryEmptyW(feFrom->szFullPath)) 1710 Win32RemoveDirectoryW(feFrom->szFullPath); 1711 } 1712 1713 static BOOL move_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo) 1714 { 1715 if (PathFileExistsW(szTo)) 1716 { 1717 if (op->req->fFlags & FOF_RENAMEONCOLLISION) 1718 { 1719 CStringW newPath = try_find_new_name(szTo); 1720 if (!newPath.IsEmpty()) 1721 { 1722 return SHNotifyMoveFileW(op, szFrom, newPath, FALSE) == 0; 1723 } 1724 } 1725 else if (!(op->req->fFlags & FOF_NOCONFIRMATION)) 1726 { 1727 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op)) 1728 return FALSE; 1729 } 1730 } 1731 1732 return SHNotifyMoveFileW(op, szFrom, szTo, FALSE) == 0; 1733 } 1734 1735 /* moves a file or directory to another directory */ 1736 static void move_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) 1737 { 1738 if (feFrom->attributes == INVALID_FILE_ATTRIBUTES) 1739 return; 1740 1741 if (!PathFileExistsW(feTo->szFullPath)) 1742 SHNotifyCreateDirectoryW(feTo->szFullPath, NULL); 1743 1744 WCHAR szDestPath[MAX_PATH]; 1745 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); 1746 1747 if (IsAttribFile(feFrom->attributes)) 1748 move_file_to_file(op, feFrom->szFullPath, szDestPath); 1749 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) 1750 move_dir_to_dir(op, feFrom, szDestPath); 1751 } 1752 1753 /* the FO_MOVE operation */ 1754 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo) 1755 { 1756 DWORD i; 1757 INT mismatched = 0; 1758 1759 const FILE_ENTRY *entryToMove; 1760 const FILE_ENTRY *fileDest; 1761 1762 if (!flFrom->dwNumFiles) 1763 return ERROR_SUCCESS; 1764 1765 if (!flTo->dwNumFiles) 1766 return ERROR_FILE_NOT_FOUND; 1767 1768 if (flFrom->bAnyDontExist) 1769 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1770 1771 if (!(multiDest) && 1772 flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1) 1773 { 1774 return ERROR_CANCELLED; 1775 } 1776 1777 if (!(multiDest) && 1778 !flFrom->bAnyDirectories && 1779 flFrom->dwNumFiles > flTo->dwNumFiles && 1780 !(flTo->bAnyDirectories && flTo->dwNumFiles == 1)) 1781 { 1782 return ERROR_CANCELLED; 1783 } 1784 1785 if (!PathFileExistsW(flTo->feFiles[0].szDirectory)) 1786 return ERROR_CANCELLED; 1787 1788 if (multiDest) 1789 mismatched = flFrom->dwNumFiles - flTo->dwNumFiles; 1790 1791 fileDest = &flTo->feFiles[0]; 1792 for (i = 0; i < flFrom->dwNumFiles; i++) 1793 { 1794 entryToMove = &flFrom->feFiles[i]; 1795 1796 if (!PathFileExistsW(fileDest->szDirectory)) 1797 return ERROR_CANCELLED; 1798 1799 if (multiDest) 1800 { 1801 if (i >= flTo->dwNumFiles) 1802 break; 1803 fileDest = &flTo->feFiles[i]; 1804 if (mismatched && !fileDest->bExists) 1805 { 1806 create_dest_dirs(flTo->feFiles[i].szFullPath); 1807 flTo->feFiles[i].bExists = TRUE; 1808 flTo->feFiles[i].attributes = FILE_ATTRIBUTE_DIRECTORY; 1809 } 1810 } 1811 1812 if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) || 1813 IsAttribDir(fileDest->attributes)) 1814 { 1815 move_to_dir(op, entryToMove, fileDest); 1816 } 1817 else if (IsAttribDir(entryToMove->attributes)) 1818 { 1819 move_dir_to_dir(op, entryToMove, fileDest->szFullPath); 1820 } 1821 else 1822 { 1823 if (!move_file_to_file(op, entryToMove->szFullPath, fileDest->szFullPath)) 1824 { 1825 op->req->fAnyOperationsAborted = TRUE; 1826 return ERROR_CANCELLED; 1827 } 1828 } 1829 1830 if (op->progress != NULL) 1831 op->bCancelled |= op->progress->HasUserCancelled(); 1832 /* Should fire on progress dialog only */ 1833 if (op->bCancelled) 1834 return ERROR_CANCELLED; 1835 1836 } 1837 1838 if (mismatched > 0) 1839 { 1840 if (flFrom->bAnyDirectories) 1841 return DE_DESTSAMETREE; 1842 else 1843 return DE_SAMEFILE; 1844 } 1845 1846 return ERROR_SUCCESS; 1847 } 1848 1849 /* the FO_RENAME files */ 1850 static HRESULT rename_files(FILE_OPERATION *op, const FILE_LIST *flFrom, const FILE_LIST *flTo) 1851 { 1852 const FILE_ENTRY *feFrom; 1853 const FILE_ENTRY *feTo; 1854 1855 if (flFrom->dwNumFiles != 1) 1856 return ERROR_GEN_FAILURE; 1857 1858 if (flTo->dwNumFiles != 1) 1859 return ERROR_CANCELLED; 1860 1861 feFrom = &flFrom->feFiles[0]; 1862 feTo= &flTo->feFiles[0]; 1863 1864 /* fail if destination doesn't exist */ 1865 if (!feFrom->bExists) 1866 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1867 1868 /* fail if destination already exists */ 1869 if (feTo->bExists) 1870 return ERROR_ALREADY_EXISTS; 1871 1872 return SHNotifyMoveFileW(op, feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes)); 1873 } 1874 1875 /* alert the user if an unsupported flag is used */ 1876 static void check_flags(FILEOP_FLAGS fFlags) 1877 { 1878 WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS | 1879 FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE | 1880 FOF_WANTMAPPINGHANDLE; 1881 1882 if (fFlags & wUnsupportedFlags) 1883 FIXME("Unsupported flags: %04x\n", fFlags); 1884 } 1885 1886 static DWORD 1887 validate_operation(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom, FILE_LIST *flTo) 1888 { 1889 DWORD i, k, dwNumDest; 1890 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1891 CStringW strTitle, strText; 1892 const FILE_ENTRY *feFrom; 1893 const FILE_ENTRY *feTo; 1894 UINT wFunc = lpFileOp->wFunc; 1895 HWND hwnd = lpFileOp->hwnd; 1896 1897 dwNumDest = flTo->dwNumFiles; 1898 1899 if (wFunc != FO_COPY && wFunc != FO_MOVE) 1900 return ERROR_SUCCESS; 1901 1902 for (k = 0; k < dwNumDest; ++k) 1903 { 1904 feTo = &flTo->feFiles[k]; 1905 for (i = 0; i < flFrom->dwNumFiles; ++i) 1906 { 1907 feFrom = &flFrom->feFiles[i]; 1908 StringCbCopyW(szFrom, sizeof(szFrom), feFrom->szFullPath); 1909 StringCbCopyW(szTo, sizeof(szTo), feTo->szFullPath); 1910 if (IsAttribDir(feTo->attributes)) 1911 { 1912 PathAppendW(szTo, feFrom->szFilename); 1913 } 1914 1915 // same path? 1916 if (lstrcmpiW(szFrom, szTo) == 0 && 1917 (wFunc == FO_MOVE || !(lpFileOp->fFlags & FOF_RENAMEONCOLLISION))) 1918 { 1919 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT))) 1920 { 1921 if (wFunc == FO_MOVE) 1922 { 1923 strTitle.LoadStringW(IDS_MOVEERRORTITLE); 1924 if (IsAttribDir(feFrom->attributes)) 1925 strText.Format(IDS_MOVEERRORSAMEFOLDER, feFrom->szFilename); 1926 else 1927 strText.Format(IDS_MOVEERRORSAME, feFrom->szFilename); 1928 } 1929 else 1930 { 1931 strTitle.LoadStringW(IDS_COPYERRORTITLE); 1932 strText.Format(IDS_COPYERRORSAME, feFrom->szFilename); 1933 return ERROR_SUCCESS; 1934 } 1935 MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR); 1936 return DE_SAMEFILE; 1937 } 1938 return DE_OPCANCELLED; 1939 } 1940 1941 // subfolder? 1942 if (IsAttribDir(feFrom->attributes)) 1943 { 1944 size_t cchFrom = PathAddBackslashW(szFrom) - szFrom; 1945 size_t cchTo = PathAddBackslashW(szTo) - szTo; 1946 if (cchFrom < cchTo) 1947 { 1948 WCHAR ch = szTo[cchFrom]; 1949 szTo[cchFrom] = 0; 1950 int compare = lstrcmpiW(szFrom, szTo); 1951 szTo[cchFrom] = ch; 1952 1953 if (compare == 0) 1954 { 1955 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT))) 1956 { 1957 if (wFunc == FO_MOVE) 1958 { 1959 strTitle.LoadStringW(IDS_MOVEERRORTITLE); 1960 strText.Format(IDS_MOVEERRORSUBFOLDER, feFrom->szFilename); 1961 } 1962 else 1963 { 1964 strTitle.LoadStringW(IDS_COPYERRORTITLE); 1965 strText.Format(IDS_COPYERRORSUBFOLDER, feFrom->szFilename); 1966 } 1967 MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR); 1968 return DE_DESTSUBTREE; 1969 } 1970 return DE_OPCANCELLED; 1971 } 1972 } 1973 } 1974 } 1975 } 1976 1977 return ERROR_SUCCESS; 1978 } 1979 1980 /************************************************************************* 1981 * SHFileOperationW [SHELL32.@] 1982 * 1983 * See SHFileOperationA 1984 */ 1985 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp) 1986 { 1987 FILE_OPERATION op; 1988 FILE_LIST flFrom, flTo; 1989 int ret = 0; 1990 1991 if (!lpFileOp) 1992 return ERROR_INVALID_PARAMETER; 1993 1994 ret = CoInitialize(NULL); 1995 if (FAILED(ret)) 1996 return ret; 1997 1998 lpFileOp->fAnyOperationsAborted = FALSE; 1999 check_flags(lpFileOp->fFlags); 2000 2001 ZeroMemory(&flFrom, sizeof(FILE_LIST)); 2002 ZeroMemory(&flTo, sizeof(FILE_LIST)); 2003 2004 if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom))) 2005 return ret; 2006 2007 if (lpFileOp->wFunc != FO_DELETE) 2008 parse_file_list(&flTo, lpFileOp->pTo); 2009 2010 ZeroMemory(&op, sizeof(op)); 2011 op.req = lpFileOp; 2012 op.totalSize.QuadPart = 0ull; 2013 op.completedSize.QuadPart = 0ull; 2014 op.bManyItems = (flFrom.dwNumFiles > 1); 2015 2016 ret = validate_operation(lpFileOp, &flFrom, &flTo); 2017 if (ret) 2018 goto cleanup; 2019 2020 if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT)) { 2021 ret = CoCreateInstance(CLSID_ProgressDialog, 2022 NULL, 2023 CLSCTX_INPROC_SERVER, 2024 IID_PPV_ARG(IProgressDialog, &op.progress)); 2025 if (FAILED(ret)) 2026 goto cleanup; 2027 2028 op.progress->StartProgressDialog(op.req->hwnd, NULL, PROGDLG_NORMAL & PROGDLG_AUTOTIME, NULL); 2029 _SetOperationTitle(&op); 2030 _FileOpCountManager(&op, &flFrom); 2031 } 2032 2033 switch (lpFileOp->wFunc) 2034 { 2035 case FO_COPY: 2036 ret = copy_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo); 2037 break; 2038 case FO_DELETE: 2039 ret = delete_files(&op, &flFrom); 2040 break; 2041 case FO_MOVE: 2042 ret = move_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo); 2043 break; 2044 case FO_RENAME: 2045 ret = rename_files(&op, &flFrom, &flTo); 2046 break; 2047 default: 2048 ret = ERROR_INVALID_PARAMETER; 2049 break; 2050 } 2051 2052 if (op.progress) { 2053 op.progress->StopProgressDialog(); 2054 op.progress->Release(); 2055 } 2056 2057 cleanup: 2058 destroy_file_list(&flFrom); 2059 2060 if (lpFileOp->wFunc != FO_DELETE) 2061 destroy_file_list(&flTo); 2062 2063 if (ret == ERROR_CANCELLED) 2064 lpFileOp->fAnyOperationsAborted = TRUE; 2065 2066 CoUninitialize(); 2067 2068 return ret; 2069 } 2070 2071 // Used by SHFreeNameMappings 2072 static int CALLBACK _DestroyCallback(void *p, void *pData) 2073 { 2074 LPSHNAMEMAPPINGW lp = (SHNAMEMAPPINGW *)p; 2075 2076 SHFree(lp->pszOldPath); 2077 SHFree(lp->pszNewPath); 2078 2079 return TRUE; 2080 } 2081 2082 /************************************************************************* 2083 * SHFreeNameMappings [shell32.246] 2084 * 2085 * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE 2086 * was specified. 2087 * 2088 * PARAMS 2089 * hNameMapping [I] handle to the name mappings used during renaming of files 2090 * 2091 * RETURNS 2092 * Nothing 2093 */ 2094 void WINAPI SHFreeNameMappings(HANDLE hNameMapping) 2095 { 2096 if (hNameMapping) 2097 { 2098 DSA_DestroyCallback((HDSA) hNameMapping, _DestroyCallback, NULL); 2099 } 2100 } 2101 2102 /************************************************************************* 2103 * SheGetDirA [SHELL32.@] 2104 * 2105 * drive = 0: returns the current directory path 2106 * drive > 0: returns the current directory path of the specified drive 2107 * drive=1 -> A: drive=2 -> B: ... 2108 * returns 0 if successful 2109 */ 2110 EXTERN_C DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer) 2111 { 2112 WCHAR org_path[MAX_PATH]; 2113 DWORD ret; 2114 char drv_path[3]; 2115 2116 /* change current directory to the specified drive */ 2117 if (drive) { 2118 strcpy(drv_path, "A:"); 2119 drv_path[0] += (char)drive-1; 2120 2121 GetCurrentDirectoryW(MAX_PATH, org_path); 2122 2123 SetCurrentDirectoryA(drv_path); 2124 } 2125 2126 /* query current directory path of the specified drive */ 2127 ret = GetCurrentDirectoryA(MAX_PATH, buffer); 2128 2129 /* back to the original drive */ 2130 if (drive) 2131 SetCurrentDirectoryW(org_path); 2132 2133 if (!ret) 2134 return GetLastError(); 2135 2136 return 0; 2137 } 2138 2139 /************************************************************************* 2140 * SheGetDirW [SHELL32.@] 2141 * 2142 * drive = 0: returns the current directory path 2143 * drive > 0: returns the current directory path of the specified drive 2144 * drive=1 -> A: drive=2 -> B: ... 2145 * returns 0 if successful 2146 */ 2147 EXTERN_C DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer) 2148 { 2149 WCHAR org_path[MAX_PATH]; 2150 DWORD ret; 2151 char drv_path[3]; 2152 2153 /* change current directory to the specified drive */ 2154 if (drive) 2155 { 2156 strcpy(drv_path, "A:"); 2157 drv_path[0] += (char)drive-1; 2158 2159 GetCurrentDirectoryW(MAX_PATH, org_path); 2160 2161 SetCurrentDirectoryA(drv_path); 2162 } 2163 2164 /* query current directory path of the specified drive */ 2165 ret = GetCurrentDirectoryW(MAX_PATH, buffer); 2166 2167 /* back to the original drive */ 2168 if (drive) 2169 SetCurrentDirectoryW(org_path); 2170 2171 if (!ret) 2172 return GetLastError(); 2173 2174 return 0; 2175 } 2176 2177 /************************************************************************* 2178 * SheChangeDirA [SHELL32.@] 2179 * 2180 * changes the current directory to the specified path 2181 * and returns 0 if successful 2182 */ 2183 EXTERN_C DWORD WINAPI SheChangeDirA(LPSTR path) 2184 { 2185 if (SetCurrentDirectoryA(path)) 2186 return 0; 2187 else 2188 return GetLastError(); 2189 } 2190 2191 /************************************************************************* 2192 * SheChangeDirW [SHELL32.@] 2193 * 2194 * changes the current directory to the specified path 2195 * and returns 0 if successful 2196 */ 2197 EXTERN_C DWORD WINAPI SheChangeDirW(LPWSTR path) 2198 { 2199 if (SetCurrentDirectoryW(path)) 2200 return 0; 2201 else 2202 return GetLastError(); 2203 } 2204 2205 /************************************************************************* 2206 * IsNetDrive [SHELL32.66] 2207 */ 2208 EXTERN_C int WINAPI IsNetDrive(int drive) 2209 { 2210 char root[4]; 2211 strcpy(root, "A:\\"); 2212 root[0] += (char)drive; 2213 return (GetDriveTypeA(root) == DRIVE_REMOTE); 2214 } 2215 2216 2217 /************************************************************************* 2218 * RealDriveType [SHELL32.524] 2219 */ 2220 EXTERN_C INT WINAPI RealDriveType(INT drive, BOOL bQueryNet) 2221 { 2222 char root[] = "A:\\"; 2223 root[0] += (char)drive; 2224 return GetDriveTypeA(root); 2225 } 2226 2227 /*********************************************************************** 2228 * SHPathPrepareForWriteW (SHELL32.@) 2229 */ 2230 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags) 2231 { 2232 DWORD res; 2233 DWORD err; 2234 LPCWSTR realpath; 2235 int len; 2236 WCHAR* last_slash; 2237 WCHAR* temppath=NULL; 2238 2239 TRACE("%p %p %s 0x%08x\n", hwnd, modless, debugstr_w(path), flags); 2240 2241 if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME)) 2242 FIXME("unimplemented flags 0x%08x\n", flags); 2243 2244 /* cut off filename if necessary */ 2245 if (flags & SHPPFW_IGNOREFILENAME) 2246 { 2247 last_slash = StrRChrW(path, NULL, '\\'); 2248 if (last_slash == NULL) 2249 len = 1; 2250 else 2251 len = last_slash - path + 1; 2252 temppath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 2253 if (!temppath) 2254 return E_OUTOFMEMORY; 2255 StrCpyNW(temppath, path, len); 2256 realpath = temppath; 2257 } 2258 else 2259 { 2260 realpath = path; 2261 } 2262 2263 /* try to create the directory if asked to */ 2264 if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE)) 2265 { 2266 if (flags & SHPPFW_ASKDIRCREATE) 2267 FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n"); 2268 2269 SHCreateDirectoryExW(0, realpath, NULL); 2270 } 2271 2272 /* check if we can access the directory */ 2273 res = GetFileAttributesW(realpath); 2274 2275 HeapFree(GetProcessHeap(), 0, temppath); 2276 2277 if (res == INVALID_FILE_ATTRIBUTES) 2278 { 2279 err = GetLastError(); 2280 if (err == ERROR_FILE_NOT_FOUND) 2281 return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); 2282 return HRESULT_FROM_WIN32(err); 2283 } 2284 else if (res & FILE_ATTRIBUTE_DIRECTORY) 2285 return S_OK; 2286 else 2287 return HRESULT_FROM_WIN32(ERROR_DIRECTORY); 2288 } 2289 2290 /*********************************************************************** 2291 * SHPathPrepareForWriteA (SHELL32.@) 2292 */ 2293 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags) 2294 { 2295 WCHAR wpath[MAX_PATH]; 2296 MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH); 2297 return SHPathPrepareForWriteW(hwnd, modless, wpath, flags); 2298 } 2299 2300 2301 /* 2302 * The two following background operations were modified from filedefext.cpp 2303 * They use an inordinate amount of mutable state across the string functions, 2304 * so are not easy to follow and care is required when modifying. 2305 */ 2306 2307 DWORD WINAPI 2308 _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *from) 2309 { 2310 DWORD ticks = GetTickCount(); 2311 FILE_ENTRY *entryToCount; 2312 2313 for (UINT i = 0; i < from->dwNumFiles; i++) 2314 { 2315 entryToCount = &from->feFiles[i]; 2316 2317 WCHAR theFileName[MAX_PATH]; 2318 StringCchCopyW(theFileName, MAX_PATH, entryToCount->szFullPath); 2319 _FileOpCount(op, theFileName, IsAttribDir(entryToCount->attributes), &ticks); 2320 } 2321 return 0; 2322 } 2323 2324 // All path manipulations, even when this function is nested, occur on the one buffer. 2325 static BOOL 2326 _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks) 2327 { 2328 /* Find filename position */ 2329 UINT cchBuf = wcslen(pwszBuf); 2330 WCHAR *pwszFilename = pwszBuf + cchBuf; 2331 size_t cchFilenameMax = MAX_PATH - cchBuf; 2332 if (!cchFilenameMax) 2333 return FALSE; 2334 2335 if (bFolder) { 2336 *(pwszFilename++) = '\\'; 2337 --cchFilenameMax; 2338 /* Find all files, FIXME: shouldn't be "*"? */ 2339 StringCchCopyW(pwszFilename, cchFilenameMax, L"*"); 2340 } 2341 2342 WIN32_FIND_DATAW wfd; 2343 HANDLE hFind = FindFirstFileW(pwszBuf, &wfd); 2344 if (hFind == INVALID_HANDLE_VALUE) 2345 { 2346 ERR("FindFirstFileW %ls failed\n", pwszBuf); 2347 return FALSE; 2348 } 2349 2350 do 2351 { 2352 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 2353 { 2354 /* Don't process "." and ".." items */ 2355 if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L"..")) 2356 continue; 2357 2358 StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName); 2359 _FileOpCount(op, pwszBuf, TRUE, ticks); 2360 } 2361 else 2362 { 2363 ULARGE_INTEGER FileSize; 2364 FileSize.u.LowPart = wfd.nFileSizeLow; 2365 FileSize.u.HighPart = wfd.nFileSizeHigh; 2366 op->totalSize.QuadPart += FileSize.QuadPart; 2367 } 2368 if (GetTickCount() - *ticks > (DWORD) 500) 2369 { 2370 // Check if the dialog has ended. If it has, we'll spin down. 2371 if (op->progress != NULL) 2372 op->bCancelled = op->progress->HasUserCancelled(); 2373 2374 if (op->bCancelled) 2375 break; 2376 *ticks = GetTickCount(); 2377 } 2378 } while(FindNextFileW(hFind, &wfd)); 2379 2380 FindClose(hFind); 2381 return TRUE; 2382 } 2383