Synchronize with trunk.
[reactos.git] / base / shell / cmd / move.c
1 /*
2 * MOVE.C - move internal command.
3 *
4 *
5 * History:
6 *
7 * 14-Dec-1998 (Eric Kohl)
8 * Started.
9 *
10 * 18-Jan-1999 (Eric Kohl)
11 * Unicode safe!
12 * Preliminary version!!!
13 *
14 * 20-Jan-1999 (Eric Kohl)
15 * Redirection safe!
16 *
17 * 27-Jan-1999 (Eric Kohl)
18 * Added help text ("/?").
19 * Added more error checks.
20 *
21 * 03-Feb-1999 (Eric Kohl)
22 * Added "/N" option.
23 *
24 * 30-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
25 * Remove all hardcoded strings in En.rc
26 *
27 * 24-Jun-2005 (Brandon Turner <turnerb7@msu.edu>)
28 * Fixed bug to allow MS style wildcards + code clean up
29 * added /y and /-y
30 */
31
32 #include "precomp.h"
33
34 #ifdef INCLUDE_CMD_MOVE
35
36 enum
37 {
38 MOVE_NOTHING = 0x001, /* /N */
39 MOVE_OVER_YES = 0x002, /* /Y */
40 MOVE_OVER_NO = 0x004, /* /-Y */
41 };
42
43 enum
44 {
45 /* Move status flags */
46 MOVE_SOURCE_IS_DIR = 0x001,
47 MOVE_SOURCE_IS_FILE = 0x002,
48 MOVE_DEST_IS_DIR = 0x004,
49 MOVE_DEST_IS_FILE = 0x008,
50 MOVE_SOURCE_HAS_WILD = 0x010, /* source has wildcard */
51 MOVE_SRC_CURRENT_IS_DIR = 0x020, /* source is file but at the current round we found a directory */
52 MOVE_DEST_EXISTS = 0x040,
53 MOVE_PATHS_ON_DIF_VOL = 0x080 /* source and destination paths are on different volume */
54 };
55
56 static INT MoveOverwrite (LPTSTR fn)
57 {
58 /* ask the user if they want to override */
59 INT res;
60 ConOutResPrintf(STRING_MOVE_HELP1, fn);
61 res = FilePromptYNA (0);
62 return res;
63 }
64
65 void GetDirectory (LPTSTR wholepath, LPTSTR directory, BOOL CheckExisting)
66 {
67 /* returns only directory part of path with backslash */
68 /* TODO: make code unc aware */
69 /* Is there a better alternative to this? */
70 LPTSTR last;
71 if (CheckExisting && IsExistingDirectory(wholepath))
72 {
73 _tcscpy(directory, wholepath);
74 }
75 else if ((last = _tcsrchr(wholepath,_T('\\'))) != NULL)
76 {
77 _tcsncpy(directory, wholepath, last - wholepath + 1);
78 directory[last - wholepath + 1] = 0;
79 }
80 else
81 {
82 GetRootPath(wholepath,directory, MAX_PATH);
83 }
84 }
85
86
87 INT cmd_move (LPTSTR param)
88 {
89 LPTSTR *arg;
90 INT argc, i, nFiles;
91 LPTSTR pszDest;
92 TCHAR szDestPath[MAX_PATH];
93 TCHAR szFullDestPath[MAX_PATH];
94 TCHAR szSrcDirPath[MAX_PATH];
95 TCHAR szSrcPath[MAX_PATH];
96 TCHAR szFullSrcPath[MAX_PATH];
97 DWORD dwFlags = 0;
98 INT nOverwrite = 0;
99 WIN32_FIND_DATA findBuffer;
100 HANDLE hFile;
101
102 /* used only when source and destination directories are on different volume */
103 HANDLE hDestFile;
104 WIN32_FIND_DATA findDestBuffer;
105 TCHAR szMoveDest[MAX_PATH];
106 TCHAR szMoveSrc[MAX_PATH];
107 LPTSTR pszDestDirPointer;
108 LPTSTR pszSrcDirPointer;
109 INT nDirLevel = 0;
110
111 LPTSTR pszFile;
112 BOOL OnlyOneFile;
113 BOOL FoundFile;
114 BOOL MoveStatus;
115 DWORD dwMoveFlags = 0;
116 DWORD dwMoveStatusFlags = 0;
117
118 if (!_tcsncmp (param, _T("/?"), 2))
119 {
120 #if 0
121 ConOutPuts (_T("Moves files and renames files and directories.\n\n"
122 "To move one or more files:\n"
123 "MOVE [/N][/Y|/-Y][drive:][path]filename1[,...] destination\n"
124 "\n"
125 "To rename a directory:\n"
126 "MOVE [/N][/Y|/-Y][drive:][path]dirname1 dirname2\n"
127 "\n"
128 " [drive:][path]filename1 Specifies the location and name of the file\n"
129 " or files you want to move.\n"
130 " /N Nothing. Don everthing but move files or direcories.\n"
131 " /Y\n"
132 " /-Y\n"
133 "..."));
134 #else
135 ConOutResPaging(TRUE,STRING_MOVE_HELP2);
136 #endif
137 return 0;
138 }
139
140 nErrorLevel = 0;
141 arg = splitspace(param, &argc);
142
143 /* read options */
144 for (i = 0; i < argc; i++)
145 {
146 if (!_tcsicmp(arg[i], _T("/N")))
147 dwFlags |= MOVE_NOTHING;
148 else if (!_tcsicmp(arg[i], _T("/Y")))
149 dwFlags |= MOVE_OVER_YES;
150 else if (!_tcsicmp(arg[i], _T("/-Y")))
151 dwFlags |= MOVE_OVER_NO;
152 else
153 break;
154 }
155 nFiles = argc - i;
156
157 if (nFiles < 1)
158 {
159 /* there must be at least one pathspec */
160 error_req_param_missing();
161 freep(arg);
162 return 1;
163 }
164
165 if (nFiles > 2)
166 {
167 /* there are more than two pathspecs */
168 error_too_many_parameters(param);
169 freep(arg);
170 return 1;
171 }
172
173 /* If no destination is given, default to current directory */
174 pszDest = (nFiles == 1) ? _T(".") : arg[i + 1];
175
176 /* check for wildcards in source and destination */
177 if (_tcschr(pszDest, _T('*')) != NULL || _tcschr(pszDest, _T('?')) != NULL)
178 {
179 /* '*'/'?' in dest, this doesnt happen. give folder name instead*/
180 error_invalid_parameter_format(pszDest);
181 freep(arg);
182 return 1;
183 }
184 if (_tcschr(arg[i], _T('*')) != NULL || _tcschr(arg[i], _T('?')) != NULL)
185 {
186 dwMoveStatusFlags |= MOVE_SOURCE_HAS_WILD;
187 }
188
189 /* get destination */
190 GetFullPathName (pszDest, MAX_PATH, szDestPath, NULL);
191 TRACE ("Destination: %s\n", debugstr_aw(szDestPath));
192
193 /* get source folder */
194 GetFullPathName(arg[i], MAX_PATH, szSrcDirPath, &pszFile);
195 if (pszFile != NULL)
196 *pszFile = _T('\0');
197 TRACE ("Source Folder: %s\n", debugstr_aw(szSrcDirPath));
198
199 hFile = FindFirstFile (arg[i], &findBuffer);
200 if (hFile == INVALID_HANDLE_VALUE)
201 {
202 ErrorMessage (GetLastError (), arg[i]);
203 freep (arg);
204 return 1;
205 }
206
207 /* check for special cases "." and ".." and if found skip them */
208 FoundFile = TRUE;
209 while(FoundFile &&
210 (_tcscmp(findBuffer.cFileName,_T(".")) == 0 ||
211 _tcscmp(findBuffer.cFileName,_T("..")) == 0))
212 FoundFile = FindNextFile (hFile, &findBuffer);
213
214 if (!FoundFile)
215 {
216 /* what? we don't have anything to move? */
217 error_file_not_found();
218 FindClose(hFile);
219 freep(arg);
220 return 1;
221 }
222
223 OnlyOneFile = TRUE;
224 /* check if there can be found files as files have first priority */
225 if (findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
226 dwMoveStatusFlags |= MOVE_SOURCE_IS_DIR;
227 else
228 dwMoveStatusFlags |= MOVE_SOURCE_IS_FILE;
229 while(OnlyOneFile && FindNextFile(hFile,&findBuffer))
230 {
231 if (!(findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
232 {
233 ConOutPrintf(_T(""));
234 if (dwMoveStatusFlags & MOVE_SOURCE_IS_FILE) OnlyOneFile = FALSE;
235 else
236 { /* this has been done this way so that we don't disturb other settings if they have been set before this */
237 dwMoveStatusFlags |= MOVE_SOURCE_IS_FILE;
238 dwMoveStatusFlags &= ~MOVE_SOURCE_IS_DIR;
239 }
240 }
241 }
242 FindClose(hFile);
243
244 TRACE ("Do we have only one file: %s\n", OnlyOneFile ? "TRUE" : "FALSE");
245
246 /* we have to start again to be sure we don't miss any files or folders*/
247 hFile = FindFirstFile (arg[i], &findBuffer);
248 if (hFile == INVALID_HANDLE_VALUE)
249 {
250 ErrorMessage (GetLastError (), arg[i]);
251 freep (arg);
252 return 1;
253 }
254
255 /* check for special cases "." and ".." and if found skip them */
256 FoundFile = TRUE;
257 while(FoundFile &&
258 (_tcscmp(findBuffer.cFileName,_T(".")) == 0 ||
259 _tcscmp(findBuffer.cFileName,_T("..")) == 0))
260 FoundFile = FindNextFile (hFile, &findBuffer);
261
262 if (!FoundFile)
263 {
264 /* huh? somebody removed files and/or folders which were there */
265 error_file_not_found();
266 FindClose(hFile);
267 freep(arg);
268 return 1;
269 }
270
271 /* check if source and destination paths are on different volumes */
272 if (szSrcDirPath[0] != szDestPath[0])
273 dwMoveStatusFlags |= MOVE_PATHS_ON_DIF_VOL;
274
275 /* move it */
276 do
277 {
278 TRACE ("Found file/directory: %s\n", debugstr_aw(findBuffer.cFileName));
279 nOverwrite = 1;
280 dwMoveFlags = 0;
281 dwMoveStatusFlags &= ~MOVE_DEST_IS_FILE &
282 ~MOVE_DEST_IS_DIR &
283 ~MOVE_SRC_CURRENT_IS_DIR &
284 ~MOVE_DEST_EXISTS;
285 _tcscpy(szFullSrcPath,szSrcDirPath);
286 if (szFullSrcPath[_tcslen(szFullSrcPath) - 1] != _T('\\'))
287 _tcscat (szFullSrcPath, _T("\\"));
288 _tcscat(szFullSrcPath,findBuffer.cFileName);
289 _tcscpy(szSrcPath, szFullSrcPath);
290
291 if (IsExistingDirectory(szSrcPath))
292 {
293 /* source is directory */
294 if (dwMoveStatusFlags & MOVE_SOURCE_IS_FILE)
295 {
296 dwMoveStatusFlags |= MOVE_SRC_CURRENT_IS_DIR; /* source is file but at the current round we found a directory */
297 continue;
298 }
299 TRACE ("Source is dir: %s\n", debugstr_aw(szSrcPath));
300 dwMoveFlags = MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED;
301 }
302
303 /* if source is file we don't need to do anything special */
304 if (IsExistingDirectory(szDestPath))
305 {
306 /* destination is existing directory */
307 TRACE ("Destination is directory: %s\n", debugstr_aw(szDestPath));
308
309 dwMoveStatusFlags |= MOVE_DEST_IS_DIR;
310
311 /*build the dest string(accounts for *)*/
312 _tcscpy (szFullDestPath, szDestPath);
313 /*check to see if there is an ending slash, if not add one*/
314 if (szFullDestPath[_tcslen(szFullDestPath) - 1] != _T('\\'))
315 _tcscat (szFullDestPath, _T("\\"));
316 _tcscat (szFullDestPath, findBuffer.cFileName);
317
318 if (IsExistingFile(szFullDestPath) || IsExistingDirectory(szFullDestPath))
319 dwMoveStatusFlags |= MOVE_DEST_EXISTS;
320
321 dwMoveFlags |= MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED;
322 }
323 if (IsExistingFile(szDestPath))
324 {
325 /* destination is a file */
326 TRACE ("Destination is file: %s\n", debugstr_aw(szDestPath));
327
328 dwMoveStatusFlags |= MOVE_DEST_IS_FILE | MOVE_DEST_EXISTS;
329 _tcscpy (szFullDestPath, szDestPath);
330
331 dwMoveFlags |= MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED;
332 }
333
334 TRACE ("Move Status Flags: 0x%X\n",dwMoveStatusFlags);
335
336 if (dwMoveStatusFlags & MOVE_SOURCE_IS_DIR &&
337 dwMoveStatusFlags & MOVE_DEST_IS_DIR &&
338 dwMoveStatusFlags & MOVE_SOURCE_HAS_WILD)
339 {
340 /* We are not allowed to have existing source and destination dir when there is wildcard in source */
341 error_syntax(NULL);
342 FindClose(hFile);
343 freep(arg);
344 return 1;
345 }
346 if (!(dwMoveStatusFlags & (MOVE_DEST_IS_FILE | MOVE_DEST_IS_DIR)))
347 {
348 /* destination doesn't exist */
349 _tcscpy (szFullDestPath, szDestPath);
350 if (dwMoveStatusFlags & MOVE_SOURCE_IS_FILE) dwMoveStatusFlags |= MOVE_DEST_IS_FILE;
351 if (dwMoveStatusFlags & MOVE_SOURCE_IS_DIR) dwMoveStatusFlags |= MOVE_DEST_IS_DIR;
352
353 dwMoveFlags |= MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED;
354 }
355
356 if (dwMoveStatusFlags & MOVE_SOURCE_IS_FILE &&
357 dwMoveStatusFlags & MOVE_DEST_IS_FILE &&
358 !OnlyOneFile)
359 {
360 /*source has many files but there is only one destination file*/
361 error_invalid_parameter_format(pszDest);
362 FindClose(hFile);
363 freep (arg);
364 return 1;
365 }
366
367 /*checks to make sure user wanted/wants the override*/
368 if ((dwFlags & MOVE_OVER_NO) &&
369 (dwMoveStatusFlags & MOVE_DEST_EXISTS))
370 continue;
371 if (!(dwFlags & MOVE_OVER_YES) &&
372 (dwMoveStatusFlags & MOVE_DEST_EXISTS))
373 nOverwrite = MoveOverwrite (szFullDestPath);
374 if (nOverwrite == PROMPT_NO || nOverwrite == PROMPT_BREAK)
375 continue;
376 if (nOverwrite == PROMPT_ALL)
377 dwFlags |= MOVE_OVER_YES;
378
379 ConOutPrintf (_T("%s => %s "), szSrcPath, szFullDestPath);
380
381 /* are we really supposed to do something */
382 if (dwFlags & MOVE_NOTHING)
383 continue;
384
385 /*move the file*/
386 if (!(dwMoveStatusFlags & MOVE_SOURCE_IS_DIR &&
387 dwMoveStatusFlags & MOVE_PATHS_ON_DIF_VOL))
388 /* we aren't moving source folder to different drive */
389 MoveStatus = MoveFileEx (szSrcPath, szFullDestPath, dwMoveFlags);
390 else
391 { /* we are moving source folder to different drive */
392 _tcscpy(szMoveDest, szFullDestPath);
393 _tcscpy(szMoveSrc, szSrcPath);
394 DeleteFile(szMoveDest);
395 MoveStatus = CreateDirectory(szMoveDest, NULL); /* we use default security settings */
396 if (MoveStatus)
397 {
398 _tcscat(szMoveDest,_T("\\"));
399 _tcscat(szMoveSrc,_T("\\"));
400 nDirLevel = 0;
401 pszDestDirPointer = szMoveDest + _tcslen(szMoveDest);
402 pszSrcDirPointer = szMoveSrc + _tcslen(szMoveSrc);
403 _tcscpy(pszSrcDirPointer,_T("*.*"));
404 hDestFile = FindFirstFile(szMoveSrc, &findDestBuffer);
405 if (hDestFile == INVALID_HANDLE_VALUE)
406 MoveStatus = FALSE;
407 else
408 {
409 BOOL FirstTime = TRUE;
410 FoundFile = TRUE;
411 MoveStatus = FALSE;
412 while(FoundFile)
413 {
414 if (FirstTime)
415 FirstTime = FALSE;
416 else
417 FoundFile = FindNextFile (hDestFile, &findDestBuffer);
418
419 if (!FoundFile)
420 {
421 /* Nothing to do in this folder so we stop working on it */
422 FindClose(hDestFile);
423 (pszSrcDirPointer)--;
424 (pszDestDirPointer)--;
425 _tcscpy(pszSrcDirPointer,_T(""));
426 _tcscpy(pszDestDirPointer,_T(""));
427 if (nDirLevel > 0)
428 {
429 TCHAR szTempPath[MAX_PATH];
430 INT_PTR nDiff;
431
432 FoundFile = TRUE; /* we need to continue our seek for files */
433 nDirLevel--;
434 RemoveDirectory(szMoveSrc);
435 GetDirectory(szMoveSrc,szTempPath,0);
436 nDiff = _tcslen(szMoveSrc) - _tcslen(szTempPath);
437 pszSrcDirPointer = pszSrcDirPointer - nDiff;
438 _tcscpy(pszSrcDirPointer,_T(""));
439 GetDirectory(szMoveDest,szTempPath,0);
440 nDiff = _tcslen(szMoveDest) - _tcslen(szTempPath);
441 pszDestDirPointer = pszDestDirPointer - nDiff;
442 _tcscpy(pszDestDirPointer,_T(""));
443 if (szMoveSrc[_tcslen(szMoveSrc) - 1] != _T('\\'))
444 _tcscat (szMoveSrc, _T("\\"));
445 if (szMoveDest[_tcslen(szMoveDest) - 1] != _T('\\'))
446 _tcscat (szMoveDest, _T("\\"));
447 pszDestDirPointer = szMoveDest + _tcslen(szMoveDest);
448 pszSrcDirPointer = szMoveSrc + _tcslen(szMoveSrc);
449 _tcscpy(pszSrcDirPointer,_T("*.*"));
450 hDestFile = FindFirstFile(szMoveSrc, &findDestBuffer);
451 if (hDestFile == INVALID_HANDLE_VALUE)
452 continue;
453 FirstTime = TRUE;
454 }
455 else
456 {
457 MoveStatus = TRUE; /* we moved everything so lets tell user about it */
458 RemoveDirectory(szMoveSrc);
459 }
460 continue;
461 }
462
463 /* if we find "." or ".." we'll skip them */
464 if (_tcscmp(findDestBuffer.cFileName,_T(".")) == 0 ||
465 _tcscmp(findDestBuffer.cFileName,_T("..")) == 0)
466 continue;
467
468 _tcscpy(pszSrcDirPointer, findDestBuffer.cFileName);
469 _tcscpy(pszDestDirPointer, findDestBuffer.cFileName);
470 if (IsExistingFile(szMoveSrc))
471 {
472 FoundFile = CopyFile(szMoveSrc, szMoveDest, FALSE);
473 if (!FoundFile) continue;
474 DeleteFile(szMoveSrc);
475 }
476 else
477 {
478 FindClose(hDestFile);
479 CreateDirectory(szMoveDest, NULL);
480 _tcscat(szMoveDest,_T("\\"));
481 _tcscat(szMoveSrc,_T("\\"));
482 nDirLevel++;
483 pszDestDirPointer = szMoveDest + _tcslen(szMoveDest);
484 pszSrcDirPointer = szMoveSrc + _tcslen(szMoveSrc);
485 _tcscpy(pszSrcDirPointer,_T("*.*"));
486 hDestFile = FindFirstFile(szMoveSrc, &findDestBuffer);
487 if (hDestFile == INVALID_HANDLE_VALUE)
488 {
489 FoundFile = FALSE;
490 continue;
491 }
492 FirstTime = TRUE;
493 }
494 }
495 }
496 }
497 }
498 if (MoveStatus)
499 ConOutResPrintf(STRING_MOVE_ERROR1);
500 else
501 ConOutResPrintf(STRING_MOVE_ERROR2);
502 }
503 while ((!OnlyOneFile || dwMoveStatusFlags & MOVE_SRC_CURRENT_IS_DIR ) &&
504 !(dwMoveStatusFlags & MOVE_SOURCE_IS_DIR) &&
505 FindNextFile (hFile, &findBuffer));
506 FindClose (hFile);
507
508 freep (arg);
509 return 0;
510 }
511
512 #endif /* INCLUDE_CMD_MOVE */