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 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 #ifdef __REACTOS__ 662 /************************************************************************ 663 * CheckForError [internal] 664 * 665 * Show message box if operation failed 666 * 667 * PARAMS 668 * op [I] File Operation context 669 * error [I] Error code 670 * src [I] Source file full name 671 * 672 */ 673 static DWORD CheckForError(FILE_OPERATION *op, DWORD error, LPCWSTR src) 674 { 675 CStringW strTitle, strMask, strText; 676 LPWSTR lpMsgBuffer; 677 678 if (error == ERROR_SUCCESS || (op->req->fFlags & (FOF_NOERRORUI | FOF_SILENT))) 679 goto exit; 680 681 strTitle.LoadStringW(op->req->wFunc == FO_COPY ? IDS_COPYERRORTITLE : IDS_MOVEERRORTITLE); 682 683 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 684 NULL, 685 error, 686 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 687 (LPWSTR)&lpMsgBuffer, 688 0, 689 NULL); 690 691 strText.Format(op->req->wFunc == FO_COPY ? IDS_COPYERROR : IDS_MOVEERROR, 692 PathFindFileNameW(src), 693 lpMsgBuffer); 694 695 MessageBoxW(op->req->hwnd, strText, strTitle, MB_ICONERROR); 696 LocalFree(lpMsgBuffer); 697 698 exit: 699 return error; 700 } 701 #endif 702 703 /************************************************************************ 704 * SHNotifyMoveFile [internal] 705 * 706 * Moves a file. Also triggers a change notify if one exists. 707 * 708 * PARAMS 709 * op [I] File Operation context 710 * src [I] path to source file to move 711 * dest [I] path to target file to move to 712 * 713 * RETURNS 714 * ERROR_SUCCESS if successful 715 */ 716 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir) 717 { 718 BOOL ret; 719 720 TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest)); 721 722 _SetOperationTexts(op, src, dest); 723 724 ret = MoveFileWithProgressW(src, dest, SHCopyProgressRoutine, op, MOVEFILE_REPLACE_EXISTING); 725 726 /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */ 727 if (!ret) 728 ret = MoveFileW(src, dest); 729 730 if (!ret) 731 { 732 DWORD dwAttr; 733 734 dwAttr = SHFindAttrW(dest, FALSE); 735 if (INVALID_FILE_ATTRIBUTES == dwAttr) 736 { 737 /* Source file may be write protected or a system file */ 738 dwAttr = GetFileAttributesW(src); 739 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) 740 if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) 741 ret = MoveFileW(src, dest); 742 } 743 } 744 if (ret) 745 { 746 SHChangeNotify(isdir ? SHCNE_MKDIR : SHCNE_CREATE, SHCNF_PATHW, dest, NULL); 747 SHChangeNotify(isdir ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_PATHW, src, NULL); 748 return ERROR_SUCCESS; 749 } 750 751 #ifdef __REACTOS__ 752 return CheckForError(op, GetLastError(), src); 753 #else 754 return GetLastError(); 755 #endif 756 } 757 758 static BOOL SHIsCdRom(LPCWSTR path) 759 { 760 WCHAR tmp[] = { L"A:\\" }; 761 762 if (!path || !path[0]) 763 return FALSE; 764 765 if (path[1] != UNICODE_NULL && path[1] != ':') 766 return FALSE; 767 768 tmp[0] = path[0]; 769 770 return GetDriveTypeW(tmp) == DRIVE_CDROM; 771 } 772 773 /************************************************************************ 774 * SHNotifyCopyFile [internal] 775 * 776 * Copies a file. Also triggers a change notify if one exists. 777 * 778 * PARAMS 779 * src [I] path to source file to move 780 * dest [I] path to target file to move to 781 * bFailIfExists [I] if TRUE, the target file will not be overwritten if 782 * a file with this name already exists 783 * 784 * RETURNS 785 * ERROR_SUCCESS if successful 786 */ 787 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists) 788 { 789 BOOL ret; 790 DWORD attribs; 791 792 TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : ""); 793 794 _SetOperationTexts(op, src, dest); 795 796 /* Destination file may already exist with read only attribute */ 797 attribs = GetFileAttributesW(dest); 798 if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY)) 799 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY); 800 801 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY) 802 { 803 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY); 804 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY) 805 { 806 TRACE("[shell32, SHNotifyCopyFileW] STILL SHIT\n"); 807 } 808 } 809 810 ret = CopyFileExW(src, dest, SHCopyProgressRoutine, op, &op->bCancelled, bFailIfExists); 811 if (ret) 812 { 813 // We are copying from a CD-ROM volume, which is readonly 814 if (SHIsCdRom(src)) 815 { 816 attribs = GetFileAttributesW(dest); 817 attribs &= ~FILE_ATTRIBUTE_READONLY; 818 SetFileAttributesW(dest, attribs); 819 } 820 821 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL); 822 return ERROR_SUCCESS; 823 } 824 825 #ifdef __REACTOS__ 826 return CheckForError(op, GetLastError(), src); 827 #else 828 return GetLastError(); 829 #endif 830 } 831 832 /************************************************************************* 833 * SHCreateDirectory [SHELL32.165] 834 * 835 * This function creates a file system folder whose fully qualified path is 836 * given by path. If one or more of the intermediate folders do not exist, 837 * they will be created as well. 838 * 839 * PARAMS 840 * hWnd [I] 841 * path [I] path of directory to create 842 * 843 * RETURNS 844 * ERROR_SUCCESS or one of the following values: 845 * ERROR_BAD_PATHNAME if the path is relative 846 * ERROR_FILE_EXISTS when a file with that name exists 847 * ERROR_PATH_NOT_FOUND can't find the path, probably invalid 848 * ERROR_INVALID_NAME if the path contains invalid chars 849 * ERROR_ALREADY_EXISTS when the directory already exists 850 * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process 851 * 852 * NOTES 853 * exported by ordinal 854 * Win9x exports ANSI 855 * WinNT/2000 exports Unicode 856 */ 857 int WINAPI SHCreateDirectory(HWND hWnd, LPCWSTR path) 858 { 859 return SHCreateDirectoryExW(hWnd, path, NULL); 860 } 861 862 /************************************************************************* 863 * SHCreateDirectoryExA [SHELL32.@] 864 * 865 * This function creates a file system folder whose fully qualified path is 866 * given by path. If one or more of the intermediate folders do not exist, 867 * they will be created as well. 868 * 869 * PARAMS 870 * hWnd [I] 871 * path [I] path of directory to create 872 * sec [I] security attributes to use or NULL 873 * 874 * RETURNS 875 * ERROR_SUCCESS or one of the following values: 876 * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative 877 * ERROR_INVALID_NAME if the path contains invalid chars 878 * ERROR_FILE_EXISTS when a file with that name exists 879 * ERROR_ALREADY_EXISTS when the directory already exists 880 * ERROR_FILENAME_EXCED_RANGE if the filename was too long to process 881 * 882 * FIXME: Not implemented yet; 883 * SHCreateDirectoryEx also verifies that the files in the directory will be visible 884 * if the path is a network path to deal with network drivers which might have a limited 885 * but unknown maximum path length. If not: 886 * 887 * If hWnd is set to a valid window handle, a message box is displayed warning 888 * the user that the files may not be accessible. If the user chooses not to 889 * proceed, the function returns ERROR_CANCELLED. 890 * 891 * If hWnd is set to NULL, no user interface is displayed and the function 892 * returns ERROR_CANCELLED. 893 */ 894 int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec) 895 { 896 LPWSTR wPath; 897 DWORD retCode; 898 899 TRACE("(%s, %p)\n", debugstr_a(path), sec); 900 901 retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); 902 if (!retCode) 903 { 904 retCode = SHCreateDirectoryExW(hWnd, wPath, sec); 905 SHELL32_FreeUnicodeBuf(wPath); 906 } 907 return retCode; 908 } 909 910 /************************************************************************* 911 * SHCreateDirectoryExW [SHELL32.@] 912 * 913 * See SHCreateDirectoryExA. 914 */ 915 int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 916 { 917 int ret = ERROR_BAD_PATHNAME; 918 TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec); 919 920 if (PathIsRelativeW(path)) 921 { 922 SetLastError(ret); 923 } 924 else 925 { 926 ret = SHNotifyCreateDirectoryW(path, sec); 927 /* Refuse to work on certain error codes before trying to create directories recursively */ 928 if (ret != ERROR_SUCCESS && 929 ret != ERROR_FILE_EXISTS && 930 ret != ERROR_ALREADY_EXISTS && 931 ret != ERROR_FILENAME_EXCED_RANGE) 932 { 933 WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; /* extra for PathAddBackslash() */ 934 935 lstrcpynW(szTemp, path, MAX_PATH); 936 pEnd = PathAddBackslashW(szTemp); 937 pSlash = szTemp + 3; 938 939 while (*pSlash) 940 { 941 while (*pSlash && *pSlash != '\\') pSlash++; 942 if (*pSlash) 943 { 944 *pSlash = 0; /* terminate path at separator */ 945 946 ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL); 947 } 948 *pSlash++ = '\\'; /* put the separator back */ 949 } 950 } 951 952 if (ret && hWnd && (ERROR_CANCELLED != ret && ERROR_ALREADY_EXISTS != ret)) 953 { 954 ShellMessageBoxW(shell32_hInstance, hWnd, MAKEINTRESOURCEW(IDS_CREATEFOLDER_DENIED), MAKEINTRESOURCEW(IDS_CREATEFOLDER_CAPTION), 955 MB_ICONEXCLAMATION | MB_OK, path); 956 ret = ERROR_CANCELLED; 957 } 958 } 959 960 return ret; 961 } 962 963 /************************************************************************* 964 * SHFindAttrW [internal] 965 * 966 * Get the Attributes for a file or directory. The difference to GetAttributes() 967 * is that this function will also work for paths containing wildcard characters 968 * in its filename. 969 970 * PARAMS 971 * path [I] path of directory or file to check 972 * fileOnly [I] TRUE if only files should be found 973 * 974 * RETURNS 975 * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of 976 * the first file or directory found otherwise 977 */ 978 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly) 979 { 980 WIN32_FIND_DATAW wfd; 981 BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, L"*?")); 982 DWORD dwAttr = INVALID_FILE_ATTRIBUTES; 983 HANDLE hFind = FindFirstFileW(pName, &wfd); 984 985 TRACE("%s %d\n", debugstr_w(pName), fileOnly); 986 if (INVALID_HANDLE_VALUE != hFind) 987 { 988 do 989 { 990 if (b_FileMask && IsAttribDir(wfd.dwFileAttributes)) 991 continue; 992 dwAttr = wfd.dwFileAttributes; 993 break; 994 } while (FindNextFileW(hFind, &wfd)); 995 996 FindClose(hFind); 997 } 998 return dwAttr; 999 } 1000 1001 /************************************************************************* 1002 * 1003 * _ConvertAtoW helper function for SHFileOperationA 1004 * 1005 * Converts a string or string-list to unicode. 1006 */ 1007 static DWORD _ConvertAtoW(PCSTR strSrc, PCWSTR* pStrDest, BOOL isList) 1008 { 1009 *pStrDest = NULL; 1010 1011 // If the input is null, nothing to convert. 1012 if (!strSrc) 1013 return 0; 1014 1015 // Measure the total size, depending on if it's a zero-terminated list. 1016 int sizeA = 0; 1017 if (isList) 1018 { 1019 PCSTR tmpSrc = strSrc; 1020 int size; 1021 do 1022 { 1023 size = lstrlenA(tmpSrc) + 1; 1024 sizeA += size; 1025 tmpSrc += size; 1026 } while (size != 1); 1027 } 1028 else 1029 { 1030 sizeA = lstrlenA(strSrc) + 1; 1031 } 1032 1033 // Measure the needed allocation size. 1034 int sizeW = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, NULL, 0); 1035 if (!sizeW) 1036 return GetLastError(); 1037 1038 PWSTR strDest = (PWSTR) HeapAlloc(GetProcessHeap(), 0, sizeW * sizeof(WCHAR)); 1039 if (!strDest) 1040 return ERROR_OUTOFMEMORY; 1041 1042 int err = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, strDest, sizeW); 1043 if (!err) 1044 { 1045 HeapFree(GetProcessHeap(), 0, strDest); 1046 return GetLastError(); 1047 } 1048 1049 *pStrDest = strDest; 1050 return 0; 1051 } 1052 1053 /************************************************************************* 1054 * SHFileOperationA [SHELL32.@] 1055 * 1056 * Function to copy, move, delete and create one or more files with optional 1057 * user prompts. 1058 * 1059 * PARAMS 1060 * lpFileOp [I/O] pointer to a structure containing all the necessary information 1061 * 1062 * RETURNS 1063 * Success: ERROR_SUCCESS. 1064 * Failure: ERROR_CANCELLED. 1065 * 1066 * NOTES 1067 * exported by name 1068 */ 1069 int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp) 1070 { 1071 int errCode, retCode; 1072 SHFILEOPSTRUCTW nFileOp = { 0 }; 1073 1074 // Convert A information to W 1075 nFileOp.hwnd = lpFileOp->hwnd; 1076 nFileOp.wFunc = lpFileOp->wFunc; 1077 nFileOp.fFlags = lpFileOp->fFlags; 1078 1079 errCode = _ConvertAtoW(lpFileOp->pFrom, &nFileOp.pFrom, TRUE); 1080 if (errCode != 0) 1081 goto cleanup; 1082 1083 if (FO_DELETE != (nFileOp.wFunc & FO_MASK)) 1084 { 1085 errCode = _ConvertAtoW(lpFileOp->pTo, &nFileOp.pTo, TRUE); 1086 if (errCode != 0) 1087 goto cleanup; 1088 } 1089 1090 if (nFileOp.fFlags & FOF_SIMPLEPROGRESS) 1091 { 1092 errCode = _ConvertAtoW(lpFileOp->lpszProgressTitle, &nFileOp.lpszProgressTitle, FALSE); 1093 if (errCode != 0) 1094 goto cleanup; 1095 } 1096 1097 // Call the actual function 1098 retCode = SHFileOperationW(&nFileOp); 1099 if (retCode) 1100 { 1101 ERR("SHFileOperationW failed with 0x%x\n", retCode); 1102 } 1103 1104 // Cleanup 1105 cleanup: 1106 if (nFileOp.pFrom) 1107 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pFrom); 1108 if (nFileOp.pTo) 1109 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pTo); 1110 if (nFileOp.lpszProgressTitle) 1111 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.lpszProgressTitle); 1112 1113 if (errCode != 0) 1114 { 1115 lpFileOp->fAnyOperationsAborted = TRUE; 1116 SetLastError(errCode); 1117 1118 return errCode; 1119 } 1120 1121 // Thankfully, starting with NT4 the name mappings are always unicode, so no need to convert. 1122 lpFileOp->hNameMappings = nFileOp.hNameMappings; 1123 lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted; 1124 return retCode; 1125 } 1126 1127 static void __inline grow_list(FILE_LIST *list) 1128 { 1129 FILE_ENTRY *newx = (FILE_ENTRY *)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles, 1130 list->num_alloc * 2 * sizeof(*newx) ); 1131 list->feFiles = newx; 1132 list->num_alloc *= 2; 1133 } 1134 1135 /* adds a file to the FILE_ENTRY struct 1136 */ 1137 static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile) 1138 { 1139 DWORD dwLen = lstrlenW(szFile) + 1; 1140 LPCWSTR ptr; 1141 LPCWSTR ptr2; 1142 1143 feFile->szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1144 lstrcpyW(feFile->szFullPath, szFile); 1145 1146 ptr = StrRChrW(szFile, NULL, '\\'); 1147 ptr2 = StrRChrW(szFile, NULL, '/'); 1148 if (!ptr || ptr < ptr2) 1149 ptr = ptr2; 1150 if (ptr) 1151 { 1152 dwLen = ptr - szFile + 1; 1153 feFile->szDirectory = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1154 lstrcpynW(feFile->szDirectory, szFile, dwLen); 1155 1156 dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1; 1157 feFile->szFilename = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1158 lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */ 1159 } 1160 feFile->bFromWildcard = FALSE; 1161 } 1162 1163 static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName) 1164 { 1165 LPCWSTR ptr; 1166 LPCWSTR ptr2; 1167 LPWSTR szFullPath; 1168 DWORD dwDirLen, dwFullLen; 1169 1170 ptr = StrRChrW(szWildCard, NULL, '\\'); 1171 ptr2 = StrRChrW(szWildCard, NULL, '/'); 1172 if (!ptr || ptr < ptr2) 1173 ptr = ptr2; 1174 dwDirLen = ptr - szWildCard + 1; 1175 1176 dwFullLen = dwDirLen + lstrlenW(szFileName) + 1; 1177 szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR)); 1178 1179 lstrcpynW(szFullPath, szWildCard, dwDirLen + 1); 1180 lstrcatW(szFullPath, szFileName); 1181 1182 return szFullPath; 1183 } 1184 1185 static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex) 1186 { 1187 WIN32_FIND_DATAW wfd; 1188 HANDLE hFile = FindFirstFileW(szFile, &wfd); 1189 FILE_ENTRY *file; 1190 LPWSTR szFullPath; 1191 BOOL res; 1192 1193 if (hFile == INVALID_HANDLE_VALUE) return; 1194 1195 for (res = TRUE; res; res = FindNextFileW(hFile, &wfd)) 1196 { 1197 if (IsDotDir(wfd.cFileName)) 1198 continue; 1199 1200 if (*pdwListIndex >= flList->num_alloc) 1201 grow_list( flList ); 1202 1203 szFullPath = wildcard_to_file(szFile, wfd.cFileName); 1204 file = &flList->feFiles[(*pdwListIndex)++]; 1205 add_file_to_entry(file, szFullPath); 1206 file->bFromWildcard = TRUE; 1207 file->attributes = wfd.dwFileAttributes; 1208 1209 if (IsAttribDir(file->attributes)) 1210 flList->bAnyDirectories = TRUE; 1211 1212 HeapFree(GetProcessHeap(), 0, szFullPath); 1213 } 1214 1215 FindClose(hFile); 1216 } 1217 1218 /* takes the null-separated file list and fills out the FILE_LIST */ 1219 static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles) 1220 { 1221 LPCWSTR ptr = szFiles; 1222 WCHAR szCurFile[MAX_PATH]; 1223 DWORD i = 0; 1224 1225 if (!szFiles) 1226 return ERROR_INVALID_PARAMETER; 1227 1228 flList->bAnyFromWildcard = FALSE; 1229 flList->bAnyDirectories = FALSE; 1230 flList->bAnyDontExist = FALSE; 1231 flList->num_alloc = 32; 1232 flList->dwNumFiles = 0; 1233 1234 /* empty list */ 1235 if (!szFiles[0]) 1236 return ERROR_ACCESS_DENIED; 1237 1238 flList->feFiles = (FILE_ENTRY *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1239 flList->num_alloc * sizeof(FILE_ENTRY)); 1240 1241 while (*ptr) 1242 { 1243 if (i >= flList->num_alloc) grow_list( flList ); 1244 1245 /* change relative to absolute path */ 1246 if (PathIsRelativeW(ptr)) 1247 { 1248 GetCurrentDirectoryW(MAX_PATH, szCurFile); 1249 PathCombineW(szCurFile, szCurFile, ptr); 1250 flList->feFiles[i].bFromRelative = TRUE; 1251 } 1252 else 1253 { 1254 lstrcpyW(szCurFile, ptr); 1255 flList->feFiles[i].bFromRelative = FALSE; 1256 } 1257 1258 /* parse wildcard files if they are in the filename */ 1259 if (StrPBrkW(szCurFile, L"*?")) 1260 { 1261 parse_wildcard_files(flList, szCurFile, &i); 1262 flList->bAnyFromWildcard = TRUE; 1263 i--; 1264 } 1265 else 1266 { 1267 FILE_ENTRY *file = &flList->feFiles[i]; 1268 add_file_to_entry(file, szCurFile); 1269 file->attributes = GetFileAttributesW( file->szFullPath ); 1270 file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES); 1271 1272 if (!file->bExists) 1273 flList->bAnyDontExist = TRUE; 1274 1275 if (IsAttribDir(file->attributes)) 1276 flList->bAnyDirectories = TRUE; 1277 } 1278 1279 /* advance to the next string */ 1280 ptr += lstrlenW(ptr) + 1; 1281 i++; 1282 } 1283 flList->dwNumFiles = i; 1284 1285 return S_OK; 1286 } 1287 1288 /* free the FILE_LIST */ 1289 static void destroy_file_list(FILE_LIST *flList) 1290 { 1291 DWORD i; 1292 1293 if (!flList || !flList->feFiles) 1294 return; 1295 1296 for (i = 0; i < flList->dwNumFiles; i++) 1297 { 1298 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory); 1299 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename); 1300 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath); 1301 } 1302 1303 HeapFree(GetProcessHeap(), 0, flList->feFiles); 1304 } 1305 1306 static CStringW try_find_new_name(LPCWSTR szDestPath) 1307 { 1308 CStringW mask(szDestPath); 1309 CStringW ext(PathFindExtensionW(szDestPath)); 1310 1311 // cut off extension before inserting a "new file" mask 1312 if (!ext.IsEmpty()) 1313 { 1314 mask = mask.Left(mask.GetLength() - ext.GetLength()); 1315 } 1316 mask += L" (%d)" + ext; 1317 1318 CStringW newName; 1319 1320 // trying to find new file name 1321 for (int i = 1; i < NEW_FILENAME_ON_COPY_TRIES; i++) 1322 { 1323 newName.Format(mask, i); 1324 1325 if (!PathFileExistsW(newName)) 1326 { 1327 return newName; 1328 } 1329 } 1330 1331 return CStringW(); 1332 } 1333 1334 static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) 1335 { 1336 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1337 FILE_LIST flFromNew, flToNew; 1338 1339 if (IsDotDir(feFrom->szFilename)) 1340 return; 1341 1342 if (PathFileExistsW(szDestPath)) 1343 PathCombineW(szTo, szDestPath, feFrom->szFilename); 1344 else 1345 lstrcpyW(szTo, szDestPath); 1346 1347 #ifdef __REACTOS__ 1348 if (PathFileExistsW(szTo)) 1349 { 1350 if (op->req->fFlags & FOF_RENAMEONCOLLISION) 1351 { 1352 CStringW newPath = try_find_new_name(szTo); 1353 if (!newPath.IsEmpty()) 1354 { 1355 StringCchCopyW(szTo, _countof(szTo), newPath); 1356 } 1357 } 1358 else if (!(op->req->fFlags & FOF_NOCONFIRMATION)) 1359 #else 1360 if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) 1361 { 1362 CStringW newPath; 1363 if (lstrcmp(feFrom->szDirectory, szDestPath) == 0 && !(newPath = try_find_new_name(szTo)).IsEmpty()) 1364 { 1365 StringCchCopyW(szTo, _countof(szTo), newPath); 1366 } 1367 else 1368 #endif 1369 { 1370 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op)) 1371 { 1372 /* Vista returns an ERROR_CANCELLED even if user pressed "No" */ 1373 if (!op->bManyItems) 1374 op->bCancelled = TRUE; 1375 return; 1376 } 1377 } 1378 } 1379 1380 szTo[lstrlenW(szTo) + 1] = '\0'; 1381 SHNotifyCreateDirectoryW(szTo, NULL); 1382 1383 PathCombineW(szFrom, feFrom->szFullPath, L"*.*"); 1384 szFrom[lstrlenW(szFrom) + 1] = '\0'; 1385 1386 ZeroMemory(&flFromNew, sizeof(FILE_LIST)); 1387 ZeroMemory(&flToNew, sizeof(FILE_LIST)); 1388 parse_file_list(&flFromNew, szFrom); 1389 parse_file_list(&flToNew, szTo); 1390 1391 copy_files(op, FALSE, &flFromNew, &flToNew); 1392 1393 destroy_file_list(&flFromNew); 1394 destroy_file_list(&flToNew); 1395 } 1396 1397 static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo) 1398 { 1399 #ifdef __REACTOS__ 1400 if (PathFileExistsW(szTo)) 1401 { 1402 if (op->req->fFlags & FOF_RENAMEONCOLLISION) 1403 { 1404 CStringW newPath = try_find_new_name(szTo); 1405 if (!newPath.IsEmpty()) 1406 { 1407 return SHNotifyCopyFileW(op, szFrom, newPath, FALSE) == 0; 1408 } 1409 } 1410 else if (!(op->req->fFlags & FOF_NOCONFIRMATION)) 1411 { 1412 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op)) 1413 return FALSE; 1414 } 1415 #else 1416 if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) 1417 { 1418 CStringW newPath; 1419 if (lstrcmp(szFrom, szTo) == 0 && !(newPath = try_find_new_name(szTo)).IsEmpty()) 1420 { 1421 return SHNotifyCopyFileW(op, szFrom, newPath, FALSE) == 0; 1422 } 1423 1424 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op)) 1425 return FALSE; 1426 #endif 1427 } 1428 1429 return SHNotifyCopyFileW(op, szFrom, szTo, FALSE) == 0; 1430 } 1431 1432 /* copy a file or directory to another directory */ 1433 static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) 1434 { 1435 if (!PathFileExistsW(feTo->szFullPath)) 1436 SHNotifyCreateDirectoryW(feTo->szFullPath, NULL); 1437 1438 if (IsAttribFile(feFrom->attributes)) 1439 { 1440 WCHAR szDestPath[MAX_PATH]; 1441 1442 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); 1443 copy_file_to_file(op, feFrom->szFullPath, szDestPath); 1444 } 1445 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) 1446 copy_dir_to_dir(op, feFrom, feTo->szFullPath); 1447 } 1448 1449 static void create_dest_dirs(LPCWSTR szDestDir) 1450 { 1451 WCHAR dir[MAX_PATH]; 1452 LPCWSTR ptr = StrChrW(szDestDir, '\\'); 1453 1454 /* make sure all directories up to last one are created */ 1455 while (ptr && (ptr = StrChrW(ptr + 1, '\\'))) 1456 { 1457 lstrcpynW(dir, szDestDir, ptr - szDestDir + 1); 1458 1459 if (!PathFileExistsW(dir)) 1460 SHNotifyCreateDirectoryW(dir, NULL); 1461 } 1462 1463 /* create last directory */ 1464 if (!PathFileExistsW(szDestDir)) 1465 SHNotifyCreateDirectoryW(szDestDir, NULL); 1466 } 1467 1468 /* the FO_COPY operation */ 1469 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo) 1470 { 1471 DWORD i; 1472 const FILE_ENTRY *entryToCopy; 1473 const FILE_ENTRY *fileDest = &flTo->feFiles[0]; 1474 1475 if (flFrom->bAnyDontExist) 1476 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1477 1478 if (flTo->dwNumFiles == 0) 1479 { 1480 /* If the destination is empty, SHFileOperation should use the current directory */ 1481 WCHAR curdir[MAX_PATH+1]; 1482 1483 GetCurrentDirectoryW(MAX_PATH, curdir); 1484 curdir[lstrlenW(curdir)+1] = 0; 1485 1486 destroy_file_list(flTo); 1487 ZeroMemory(flTo, sizeof(FILE_LIST)); 1488 parse_file_list(flTo, curdir); 1489 fileDest = &flTo->feFiles[0]; 1490 } 1491 1492 if (multiDest) 1493 { 1494 if (flFrom->bAnyFromWildcard) 1495 return ERROR_CANCELLED; 1496 1497 if (flFrom->dwNumFiles != flTo->dwNumFiles) 1498 { 1499 if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) 1500 return ERROR_CANCELLED; 1501 1502 /* Free all but the first entry. */ 1503 for (i = 1; i < flTo->dwNumFiles; i++) 1504 { 1505 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory); 1506 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename); 1507 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath); 1508 } 1509 1510 flTo->dwNumFiles = 1; 1511 } 1512 else if (IsAttribDir(fileDest->attributes)) 1513 { 1514 for (i = 1; i < flTo->dwNumFiles; i++) 1515 if (!IsAttribDir(flTo->feFiles[i].attributes) || 1516 !IsAttribDir(flFrom->feFiles[i].attributes)) 1517 { 1518 return ERROR_CANCELLED; 1519 } 1520 } 1521 } 1522 else if (flFrom->dwNumFiles != 1) 1523 { 1524 if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) 1525 return ERROR_CANCELLED; 1526 1527 if (PathFileExistsW(fileDest->szFullPath) && 1528 IsAttribFile(fileDest->attributes)) 1529 { 1530 return ERROR_CANCELLED; 1531 } 1532 1533 if (flTo->dwNumFiles == 1 && fileDest->bFromRelative && 1534 !PathFileExistsW(fileDest->szFullPath)) 1535 { 1536 return ERROR_CANCELLED; 1537 } 1538 } 1539 1540 for (i = 0; i < flFrom->dwNumFiles; i++) 1541 { 1542 entryToCopy = &flFrom->feFiles[i]; 1543 1544 if ((multiDest) && 1545 flTo->dwNumFiles > 1) 1546 { 1547 fileDest = &flTo->feFiles[i]; 1548 } 1549 1550 if (IsAttribDir(entryToCopy->attributes) && 1551 !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory)) 1552 { 1553 return ERROR_SUCCESS; 1554 } 1555 1556 create_dest_dirs(fileDest->szDirectory); 1557 1558 if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath)) 1559 { 1560 if (IsAttribFile(entryToCopy->attributes)) 1561 return ERROR_NO_MORE_SEARCH_HANDLES; 1562 else 1563 return ERROR_SUCCESS; 1564 } 1565 1566 if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) || 1567 IsAttribDir(fileDest->attributes)) 1568 { 1569 copy_to_dir(op, entryToCopy, fileDest); 1570 } 1571 else if (IsAttribDir(entryToCopy->attributes)) 1572 { 1573 copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath); 1574 } 1575 else 1576 { 1577 if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath)) 1578 { 1579 op->req->fAnyOperationsAborted = TRUE; 1580 return ERROR_CANCELLED; 1581 } 1582 } 1583 1584 if (op->progress != NULL) 1585 op->bCancelled |= op->progress->HasUserCancelled(); 1586 /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */ 1587 if (op->bCancelled) 1588 return ERROR_CANCELLED; 1589 } 1590 1591 /* Vista return code. On XP if the used pressed "No" for the last item, 1592 * ERROR_ARENA_TRASHED would be returned */ 1593 return ERROR_SUCCESS; 1594 } 1595 1596 static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom) 1597 { 1598 if (flFrom->dwNumFiles > 1) 1599 { 1600 WCHAR tmp[8]; 1601 1602 wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), L"%d", flFrom->dwNumFiles); 1603 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL); 1604 } 1605 else 1606 { 1607 const FILE_ENTRY *fileEntry = &flFrom->feFiles[0]; 1608 1609 if (IsAttribFile(fileEntry->attributes)) 1610 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL); 1611 else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) 1612 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL); 1613 } 1614 return TRUE; 1615 } 1616 1617 /* the FO_DELETE operation */ 1618 static HRESULT delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom) 1619 { 1620 const FILE_ENTRY *fileEntry; 1621 DWORD i; 1622 BOOL bPathExists; 1623 BOOL bTrash; 1624 1625 if (!flFrom->dwNumFiles) 1626 return ERROR_SUCCESS; 1627 1628 /* Windows also checks only the first item */ 1629 bTrash = (op->req->fFlags & FOF_ALLOWUNDO) 1630 && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath); 1631 1632 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (!bTrash && op->req->fFlags & FOF_WANTNUKEWARNING)) 1633 if (!confirm_delete_list(op->req->hwnd, op->req->fFlags, bTrash, flFrom)) 1634 { 1635 op->req->fAnyOperationsAborted = TRUE; 1636 return 0; 1637 } 1638 1639 /* Check files. Do not delete one if one file does not exists */ 1640 for (i = 0; i < flFrom->dwNumFiles; i++) 1641 { 1642 fileEntry = &flFrom->feFiles[i]; 1643 1644 if (fileEntry->attributes == (ULONG)-1) 1645 { 1646 // This is a windows 2003 server specific value which has been removed. 1647 // Later versions of windows return ERROR_FILE_NOT_FOUND. 1648 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1649 } 1650 } 1651 1652 for (i = 0; i < flFrom->dwNumFiles; i++) 1653 { 1654 bPathExists = TRUE; 1655 fileEntry = &flFrom->feFiles[i]; 1656 1657 if (!IsAttribFile(fileEntry->attributes) && 1658 (op->req->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) 1659 continue; 1660 1661 if (bTrash) 1662 { 1663 BOOL bDelete; 1664 if (TRASH_TrashFile(fileEntry->szFullPath)) 1665 { 1666 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL); 1667 continue; 1668 } 1669 1670 /* Note: Windows silently deletes the file in such a situation, we show a dialog */ 1671 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (op->req->fFlags & FOF_WANTNUKEWARNING)) 1672 bDelete = SHELL_ConfirmDialogW(op->req->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL); 1673 else 1674 bDelete = TRUE; 1675 1676 if (!bDelete) 1677 { 1678 op->req->fAnyOperationsAborted = TRUE; 1679 break; 1680 } 1681 } 1682 1683 /* delete the file or directory */ 1684 if (IsAttribFile(fileEntry->attributes)) 1685 { 1686 bPathExists = (ERROR_SUCCESS == SHNotifyDeleteFileW(op, fileEntry->szFullPath)); 1687 } 1688 else 1689 bPathExists = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE); 1690 1691 if (!bPathExists) 1692 { 1693 DWORD err = GetLastError(); 1694 1695 if (ERROR_FILE_NOT_FOUND == err) 1696 { 1697 // This is a windows 2003 server specific value which ahs been removed. 1698 // Later versions of windows return ERROR_FILE_NOT_FOUND. 1699 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1700 } 1701 else 1702 { 1703 return err; 1704 } 1705 } 1706 1707 if (op->progress != NULL) 1708 op->bCancelled |= op->progress->HasUserCancelled(); 1709 /* Should fire on progress dialog only */ 1710 if (op->bCancelled) 1711 return ERROR_CANCELLED; 1712 } 1713 1714 return ERROR_SUCCESS; 1715 } 1716 1717 static void move_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) 1718 { 1719 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1720 FILE_LIST flFromNew, flToNew; 1721 1722 if (IsDotDir(feFrom->szFilename)) 1723 return; 1724 1725 SHNotifyCreateDirectoryW(szDestPath, NULL); 1726 1727 PathCombineW(szFrom, feFrom->szFullPath, L"*.*"); 1728 szFrom[lstrlenW(szFrom) + 1] = '\0'; 1729 1730 lstrcpyW(szTo, szDestPath); 1731 szTo[lstrlenW(szDestPath) + 1] = '\0'; 1732 1733 ZeroMemory(&flFromNew, sizeof(FILE_LIST)); 1734 ZeroMemory(&flToNew, sizeof(FILE_LIST)); 1735 parse_file_list(&flFromNew, szFrom); 1736 parse_file_list(&flToNew, szTo); 1737 1738 move_files(op, FALSE, &flFromNew, &flToNew); 1739 1740 destroy_file_list(&flFromNew); 1741 destroy_file_list(&flToNew); 1742 1743 if (PathIsDirectoryEmptyW(feFrom->szFullPath)) 1744 Win32RemoveDirectoryW(feFrom->szFullPath); 1745 } 1746 1747 /* moves a file or directory to another directory */ 1748 static void move_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) 1749 { 1750 WCHAR szDestPath[MAX_PATH]; 1751 1752 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); 1753 1754 if (IsAttribFile(feFrom->attributes)) 1755 SHNotifyMoveFileW(op, feFrom->szFullPath, szDestPath, FALSE); 1756 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) 1757 move_dir_to_dir(op, feFrom, szDestPath); 1758 } 1759 1760 /* the FO_MOVE operation */ 1761 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo) 1762 { 1763 DWORD i; 1764 INT mismatched = 0; 1765 1766 const FILE_ENTRY *entryToMove; 1767 const FILE_ENTRY *fileDest; 1768 1769 if (!flFrom->dwNumFiles) 1770 return ERROR_SUCCESS; 1771 1772 if (!flTo->dwNumFiles) 1773 return ERROR_FILE_NOT_FOUND; 1774 1775 if (!(multiDest) && 1776 flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1) 1777 { 1778 return ERROR_CANCELLED; 1779 } 1780 1781 if (!(multiDest) && 1782 !flFrom->bAnyDirectories && 1783 flFrom->dwNumFiles > flTo->dwNumFiles && 1784 !(flTo->bAnyDirectories && flTo->dwNumFiles == 1)) 1785 { 1786 return ERROR_CANCELLED; 1787 } 1788 1789 if (!PathFileExistsW(flTo->feFiles[0].szDirectory)) 1790 return ERROR_CANCELLED; 1791 1792 if (multiDest) 1793 mismatched = flFrom->dwNumFiles - flTo->dwNumFiles; 1794 1795 fileDest = &flTo->feFiles[0]; 1796 for (i = 0; i < flFrom->dwNumFiles; i++) 1797 { 1798 entryToMove = &flFrom->feFiles[i]; 1799 1800 if (!PathFileExistsW(fileDest->szDirectory)) 1801 return ERROR_CANCELLED; 1802 1803 if (multiDest) 1804 { 1805 if (i >= flTo->dwNumFiles) 1806 break; 1807 fileDest = &flTo->feFiles[i]; 1808 if (mismatched && !fileDest->bExists) 1809 { 1810 create_dest_dirs(flTo->feFiles[i].szFullPath); 1811 flTo->feFiles[i].bExists = TRUE; 1812 flTo->feFiles[i].attributes = FILE_ATTRIBUTE_DIRECTORY; 1813 } 1814 } 1815 1816 if (fileDest->bExists && IsAttribDir(fileDest->attributes)) 1817 move_to_dir(op, entryToMove, fileDest); 1818 else 1819 SHNotifyMoveFileW(op, entryToMove->szFullPath, fileDest->szFullPath, IsAttribDir(entryToMove->attributes)); 1820 1821 if (op->progress != NULL) 1822 op->bCancelled |= op->progress->HasUserCancelled(); 1823 /* Should fire on progress dialog only */ 1824 if (op->bCancelled) 1825 return ERROR_CANCELLED; 1826 1827 } 1828 1829 if (mismatched > 0) 1830 { 1831 if (flFrom->bAnyDirectories) 1832 return DE_DESTSAMETREE; 1833 else 1834 return DE_SAMEFILE; 1835 } 1836 1837 return ERROR_SUCCESS; 1838 } 1839 1840 /* the FO_RENAME files */ 1841 static HRESULT rename_files(FILE_OPERATION *op, const FILE_LIST *flFrom, const FILE_LIST *flTo) 1842 { 1843 const FILE_ENTRY *feFrom; 1844 const FILE_ENTRY *feTo; 1845 1846 if (flFrom->dwNumFiles != 1) 1847 return ERROR_GEN_FAILURE; 1848 1849 if (flTo->dwNumFiles != 1) 1850 return ERROR_CANCELLED; 1851 1852 feFrom = &flFrom->feFiles[0]; 1853 feTo= &flTo->feFiles[0]; 1854 1855 /* fail if destination doesn't exist */ 1856 if (!feFrom->bExists) 1857 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1858 1859 /* fail if destination already exists */ 1860 if (feTo->bExists) 1861 return ERROR_ALREADY_EXISTS; 1862 1863 return SHNotifyMoveFileW(op, feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes)); 1864 } 1865 1866 /* alert the user if an unsupported flag is used */ 1867 static void check_flags(FILEOP_FLAGS fFlags) 1868 { 1869 WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS | 1870 FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE | 1871 #ifdef __REACTOS__ 1872 FOF_WANTMAPPINGHANDLE; 1873 #else 1874 FOF_RENAMEONCOLLISION | FOF_WANTMAPPINGHANDLE; 1875 #endif 1876 1877 if (fFlags & wUnsupportedFlags) 1878 FIXME("Unsupported flags: %04x\n", fFlags); 1879 } 1880 1881 #ifdef __REACTOS__ 1882 1883 static DWORD 1884 validate_operation(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom, FILE_LIST *flTo) 1885 { 1886 DWORD i, k, dwNumDest; 1887 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1888 CStringW strTitle, strText; 1889 const FILE_ENTRY *feFrom; 1890 const FILE_ENTRY *feTo; 1891 UINT wFunc = lpFileOp->wFunc; 1892 HWND hwnd = lpFileOp->hwnd; 1893 1894 dwNumDest = flTo->dwNumFiles; 1895 1896 if (wFunc != FO_COPY && wFunc != FO_MOVE) 1897 return ERROR_SUCCESS; 1898 1899 for (k = 0; k < dwNumDest; ++k) 1900 { 1901 feTo = &flTo->feFiles[k]; 1902 for (i = 0; i < flFrom->dwNumFiles; ++i) 1903 { 1904 feFrom = &flFrom->feFiles[i]; 1905 StringCbCopyW(szFrom, sizeof(szFrom), feFrom->szFullPath); 1906 StringCbCopyW(szTo, sizeof(szTo), feTo->szFullPath); 1907 if (IsAttribDir(feTo->attributes)) 1908 { 1909 PathAppendW(szTo, feFrom->szFilename); 1910 } 1911 1912 // same path? 1913 if (lstrcmpiW(szFrom, szTo) == 0 && 1914 (wFunc == FO_MOVE || !(lpFileOp->fFlags & FOF_RENAMEONCOLLISION))) 1915 { 1916 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT))) 1917 { 1918 if (wFunc == FO_MOVE) 1919 { 1920 strTitle.LoadStringW(IDS_MOVEERRORTITLE); 1921 if (IsAttribDir(feFrom->attributes)) 1922 strText.Format(IDS_MOVEERRORSAMEFOLDER, feFrom->szFilename); 1923 else 1924 strText.Format(IDS_MOVEERRORSAME, feFrom->szFilename); 1925 } 1926 else 1927 { 1928 strTitle.LoadStringW(IDS_COPYERRORTITLE); 1929 strText.Format(IDS_COPYERRORSAME, feFrom->szFilename); 1930 return ERROR_SUCCESS; 1931 } 1932 MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR); 1933 return DE_SAMEFILE; 1934 } 1935 return DE_OPCANCELLED; 1936 } 1937 1938 // subfolder? 1939 if (IsAttribDir(feFrom->attributes)) 1940 { 1941 size_t cchFrom = PathAddBackslashW(szFrom) - szFrom; 1942 size_t cchTo = PathAddBackslashW(szTo) - szTo; 1943 if (cchFrom < cchTo) 1944 { 1945 WCHAR ch = szTo[cchFrom]; 1946 szTo[cchFrom] = 0; 1947 int compare = lstrcmpiW(szFrom, szTo); 1948 szTo[cchFrom] = ch; 1949 1950 if (compare == 0) 1951 { 1952 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT))) 1953 { 1954 if (wFunc == FO_MOVE) 1955 { 1956 strTitle.LoadStringW(IDS_MOVEERRORTITLE); 1957 strText.Format(IDS_MOVEERRORSUBFOLDER, feFrom->szFilename); 1958 } 1959 else 1960 { 1961 strTitle.LoadStringW(IDS_COPYERRORTITLE); 1962 strText.Format(IDS_COPYERRORSUBFOLDER, feFrom->szFilename); 1963 } 1964 MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR); 1965 return DE_DESTSUBTREE; 1966 } 1967 return DE_OPCANCELLED; 1968 } 1969 } 1970 } 1971 } 1972 } 1973 1974 return ERROR_SUCCESS; 1975 } 1976 #endif 1977 /************************************************************************* 1978 * SHFileOperationW [SHELL32.@] 1979 * 1980 * See SHFileOperationA 1981 */ 1982 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp) 1983 { 1984 FILE_OPERATION op; 1985 FILE_LIST flFrom, flTo; 1986 int ret = 0; 1987 1988 if (!lpFileOp) 1989 return ERROR_INVALID_PARAMETER; 1990 1991 ret = CoInitialize(NULL); 1992 if (FAILED(ret)) 1993 return ret; 1994 1995 lpFileOp->fAnyOperationsAborted = FALSE; 1996 check_flags(lpFileOp->fFlags); 1997 1998 ZeroMemory(&flFrom, sizeof(FILE_LIST)); 1999 ZeroMemory(&flTo, sizeof(FILE_LIST)); 2000 2001 if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom))) 2002 return ret; 2003 2004 if (lpFileOp->wFunc != FO_DELETE) 2005 parse_file_list(&flTo, lpFileOp->pTo); 2006 2007 ZeroMemory(&op, sizeof(op)); 2008 op.req = lpFileOp; 2009 op.totalSize.QuadPart = 0ull; 2010 op.completedSize.QuadPart = 0ull; 2011 op.bManyItems = (flFrom.dwNumFiles > 1); 2012 2013 #ifdef __REACTOS__ 2014 ret = validate_operation(lpFileOp, &flFrom, &flTo); 2015 if (ret) 2016 goto cleanup; 2017 #endif 2018 if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT)) { 2019 ret = CoCreateInstance(CLSID_ProgressDialog, 2020 NULL, 2021 CLSCTX_INPROC_SERVER, 2022 IID_PPV_ARG(IProgressDialog, &op.progress)); 2023 if (FAILED(ret)) 2024 goto cleanup; 2025 2026 op.progress->StartProgressDialog(op.req->hwnd, NULL, PROGDLG_NORMAL & PROGDLG_AUTOTIME, NULL); 2027 _SetOperationTitle(&op); 2028 _FileOpCountManager(&op, &flFrom); 2029 } 2030 2031 switch (lpFileOp->wFunc) 2032 { 2033 case FO_COPY: 2034 ret = copy_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo); 2035 break; 2036 case FO_DELETE: 2037 ret = delete_files(&op, &flFrom); 2038 break; 2039 case FO_MOVE: 2040 ret = move_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo); 2041 break; 2042 case FO_RENAME: 2043 ret = rename_files(&op, &flFrom, &flTo); 2044 break; 2045 default: 2046 ret = ERROR_INVALID_PARAMETER; 2047 break; 2048 } 2049 2050 if (op.progress) { 2051 op.progress->StopProgressDialog(); 2052 op.progress->Release(); 2053 } 2054 2055 cleanup: 2056 destroy_file_list(&flFrom); 2057 2058 if (lpFileOp->wFunc != FO_DELETE) 2059 destroy_file_list(&flTo); 2060 2061 if (ret == ERROR_CANCELLED) 2062 lpFileOp->fAnyOperationsAborted = TRUE; 2063 2064 CoUninitialize(); 2065 2066 return ret; 2067 } 2068 2069 // Used by SHFreeNameMappings 2070 static int CALLBACK _DestroyCallback(void *p, void *pData) 2071 { 2072 LPSHNAMEMAPPINGW lp = (SHNAMEMAPPINGW *)p; 2073 2074 SHFree(lp->pszOldPath); 2075 SHFree(lp->pszNewPath); 2076 2077 return TRUE; 2078 } 2079 2080 /************************************************************************* 2081 * SHFreeNameMappings [shell32.246] 2082 * 2083 * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE 2084 * was specified. 2085 * 2086 * PARAMS 2087 * hNameMapping [I] handle to the name mappings used during renaming of files 2088 * 2089 * RETURNS 2090 * Nothing 2091 */ 2092 void WINAPI SHFreeNameMappings(HANDLE hNameMapping) 2093 { 2094 if (hNameMapping) 2095 { 2096 DSA_DestroyCallback((HDSA) hNameMapping, _DestroyCallback, NULL); 2097 } 2098 } 2099 2100 /************************************************************************* 2101 * SheGetDirA [SHELL32.@] 2102 * 2103 * drive = 0: returns the current directory path 2104 * drive > 0: returns the current directory path of the specified drive 2105 * drive=1 -> A: drive=2 -> B: ... 2106 * returns 0 if successful 2107 */ 2108 EXTERN_C DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer) 2109 { 2110 WCHAR org_path[MAX_PATH]; 2111 DWORD ret; 2112 char drv_path[3]; 2113 2114 /* change current directory to the specified drive */ 2115 if (drive) { 2116 strcpy(drv_path, "A:"); 2117 drv_path[0] += (char)drive-1; 2118 2119 GetCurrentDirectoryW(MAX_PATH, org_path); 2120 2121 SetCurrentDirectoryA(drv_path); 2122 } 2123 2124 /* query current directory path of the specified drive */ 2125 ret = GetCurrentDirectoryA(MAX_PATH, buffer); 2126 2127 /* back to the original drive */ 2128 if (drive) 2129 SetCurrentDirectoryW(org_path); 2130 2131 if (!ret) 2132 return GetLastError(); 2133 2134 return 0; 2135 } 2136 2137 /************************************************************************* 2138 * SheGetDirW [SHELL32.@] 2139 * 2140 * drive = 0: returns the current directory path 2141 * drive > 0: returns the current directory path of the specified drive 2142 * drive=1 -> A: drive=2 -> B: ... 2143 * returns 0 if successful 2144 */ 2145 EXTERN_C DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer) 2146 { 2147 WCHAR org_path[MAX_PATH]; 2148 DWORD ret; 2149 char drv_path[3]; 2150 2151 /* change current directory to the specified drive */ 2152 if (drive) 2153 { 2154 strcpy(drv_path, "A:"); 2155 drv_path[0] += (char)drive-1; 2156 2157 GetCurrentDirectoryW(MAX_PATH, org_path); 2158 2159 SetCurrentDirectoryA(drv_path); 2160 } 2161 2162 /* query current directory path of the specified drive */ 2163 ret = GetCurrentDirectoryW(MAX_PATH, buffer); 2164 2165 /* back to the original drive */ 2166 if (drive) 2167 SetCurrentDirectoryW(org_path); 2168 2169 if (!ret) 2170 return GetLastError(); 2171 2172 return 0; 2173 } 2174 2175 /************************************************************************* 2176 * SheChangeDirA [SHELL32.@] 2177 * 2178 * changes the current directory to the specified path 2179 * and returns 0 if successful 2180 */ 2181 EXTERN_C DWORD WINAPI SheChangeDirA(LPSTR path) 2182 { 2183 if (SetCurrentDirectoryA(path)) 2184 return 0; 2185 else 2186 return GetLastError(); 2187 } 2188 2189 /************************************************************************* 2190 * SheChangeDirW [SHELL32.@] 2191 * 2192 * changes the current directory to the specified path 2193 * and returns 0 if successful 2194 */ 2195 EXTERN_C DWORD WINAPI SheChangeDirW(LPWSTR path) 2196 { 2197 if (SetCurrentDirectoryW(path)) 2198 return 0; 2199 else 2200 return GetLastError(); 2201 } 2202 2203 /************************************************************************* 2204 * IsNetDrive [SHELL32.66] 2205 */ 2206 EXTERN_C int WINAPI IsNetDrive(int drive) 2207 { 2208 char root[4]; 2209 strcpy(root, "A:\\"); 2210 root[0] += (char)drive; 2211 return (GetDriveTypeA(root) == DRIVE_REMOTE); 2212 } 2213 2214 2215 /************************************************************************* 2216 * RealDriveType [SHELL32.524] 2217 */ 2218 EXTERN_C INT WINAPI RealDriveType(INT drive, BOOL bQueryNet) 2219 { 2220 char root[] = "A:\\"; 2221 root[0] += (char)drive; 2222 return GetDriveTypeA(root); 2223 } 2224 2225 /*********************************************************************** 2226 * SHPathPrepareForWriteW (SHELL32.@) 2227 */ 2228 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags) 2229 { 2230 DWORD res; 2231 DWORD err; 2232 LPCWSTR realpath; 2233 int len; 2234 WCHAR* last_slash; 2235 WCHAR* temppath=NULL; 2236 2237 TRACE("%p %p %s 0x%08x\n", hwnd, modless, debugstr_w(path), flags); 2238 2239 if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME)) 2240 FIXME("unimplemented flags 0x%08x\n", flags); 2241 2242 /* cut off filename if necessary */ 2243 if (flags & SHPPFW_IGNOREFILENAME) 2244 { 2245 last_slash = StrRChrW(path, NULL, '\\'); 2246 if (last_slash == NULL) 2247 len = 1; 2248 else 2249 len = last_slash - path + 1; 2250 temppath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 2251 if (!temppath) 2252 return E_OUTOFMEMORY; 2253 StrCpyNW(temppath, path, len); 2254 realpath = temppath; 2255 } 2256 else 2257 { 2258 realpath = path; 2259 } 2260 2261 /* try to create the directory if asked to */ 2262 if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE)) 2263 { 2264 if (flags & SHPPFW_ASKDIRCREATE) 2265 FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n"); 2266 2267 SHCreateDirectoryExW(0, realpath, NULL); 2268 } 2269 2270 /* check if we can access the directory */ 2271 res = GetFileAttributesW(realpath); 2272 2273 HeapFree(GetProcessHeap(), 0, temppath); 2274 2275 if (res == INVALID_FILE_ATTRIBUTES) 2276 { 2277 err = GetLastError(); 2278 if (err == ERROR_FILE_NOT_FOUND) 2279 return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); 2280 return HRESULT_FROM_WIN32(err); 2281 } 2282 else if (res & FILE_ATTRIBUTE_DIRECTORY) 2283 return S_OK; 2284 else 2285 return HRESULT_FROM_WIN32(ERROR_DIRECTORY); 2286 } 2287 2288 /*********************************************************************** 2289 * SHPathPrepareForWriteA (SHELL32.@) 2290 */ 2291 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags) 2292 { 2293 WCHAR wpath[MAX_PATH]; 2294 MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH); 2295 return SHPathPrepareForWriteW(hwnd, modless, wpath, flags); 2296 } 2297 2298 2299 /* 2300 * The two following background operations were modified from filedefext.cpp 2301 * They use an inordinate amount of mutable state across the string functions, 2302 * so are not easy to follow and care is required when modifying. 2303 */ 2304 2305 DWORD WINAPI 2306 _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *from) 2307 { 2308 DWORD ticks = GetTickCount(); 2309 FILE_ENTRY *entryToCount; 2310 2311 for (UINT i = 0; i < from->dwNumFiles; i++) 2312 { 2313 entryToCount = &from->feFiles[i]; 2314 2315 WCHAR theFileName[MAX_PATH]; 2316 StringCchCopyW(theFileName, MAX_PATH, entryToCount->szFullPath); 2317 _FileOpCount(op, theFileName, IsAttribDir(entryToCount->attributes), &ticks); 2318 } 2319 return 0; 2320 } 2321 2322 // All path manipulations, even when this function is nested, occur on the one buffer. 2323 static BOOL 2324 _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks) 2325 { 2326 /* Find filename position */ 2327 UINT cchBuf = wcslen(pwszBuf); 2328 WCHAR *pwszFilename = pwszBuf + cchBuf; 2329 size_t cchFilenameMax = MAX_PATH - cchBuf; 2330 if (!cchFilenameMax) 2331 return FALSE; 2332 2333 if (bFolder) { 2334 *(pwszFilename++) = '\\'; 2335 --cchFilenameMax; 2336 /* Find all files, FIXME: shouldn't be "*"? */ 2337 StringCchCopyW(pwszFilename, cchFilenameMax, L"*"); 2338 } 2339 2340 WIN32_FIND_DATAW wfd; 2341 HANDLE hFind = FindFirstFileW(pwszBuf, &wfd); 2342 if (hFind == INVALID_HANDLE_VALUE) 2343 { 2344 ERR("FindFirstFileW %ls failed\n", pwszBuf); 2345 return FALSE; 2346 } 2347 2348 do 2349 { 2350 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 2351 { 2352 /* Don't process "." and ".." items */ 2353 if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L"..")) 2354 continue; 2355 2356 StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName); 2357 _FileOpCount(op, pwszBuf, TRUE, ticks); 2358 } 2359 else 2360 { 2361 ULARGE_INTEGER FileSize; 2362 FileSize.u.LowPart = wfd.nFileSizeLow; 2363 FileSize.u.HighPart = wfd.nFileSizeHigh; 2364 op->totalSize.QuadPart += FileSize.QuadPart; 2365 } 2366 if (GetTickCount() - *ticks > (DWORD) 500) 2367 { 2368 // Check if the dialog has ended. If it has, we'll spin down. 2369 if (op->progress != NULL) 2370 op->bCancelled = op->progress->HasUserCancelled(); 2371 2372 if (op->bCancelled) 2373 break; 2374 *ticks = GetTickCount(); 2375 } 2376 } while(FindNextFileW(hFind, &wfd)); 2377 2378 FindClose(hFind); 2379 return TRUE; 2380 } 2381