xref: /reactos/base/shell/cmd/move.c (revision 40462c92)
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 = NULL;
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 directories.\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         nErrorLevel = 1;
162         goto Quit;
163     }
164 
165     if (nFiles > 2)
166     {
167         /* there are more than two pathspecs */
168         error_too_many_parameters(param);
169         nErrorLevel = 1;
170         goto Quit;
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         nErrorLevel = 1;
182         goto Quit;
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         nErrorLevel = 1;
204         goto Quit;
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         nErrorLevel = 1;
220         goto Quit;
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         nErrorLevel = 1;
252         goto Quit;
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         nErrorLevel = 1;
268         goto Quit;
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             nErrorLevel = 1;
344             goto Quit;
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             nErrorLevel = 1;
364             goto Quit;
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 
499         if (MoveStatus)
500         {
501             ConOutResPrintf(STRING_MOVE_ERROR1);
502         }
503         else
504         {
505             ConOutResPrintf(STRING_MOVE_ERROR2);
506             nErrorLevel = 1;
507         }
508     }
509     while ((!OnlyOneFile || dwMoveStatusFlags & MOVE_SRC_CURRENT_IS_DIR ) &&
510             !(dwMoveStatusFlags & MOVE_SOURCE_IS_DIR) &&
511             FindNextFile (hFile, &findBuffer));
512     FindClose (hFile);
513 
514     if(hDestFile && hDestFile != INVALID_HANDLE_VALUE)
515         FindClose(hDestFile);
516 
517 Quit:
518     freep(arg);
519     return nErrorLevel;
520 }
521 
522 #endif /* INCLUDE_CMD_MOVE */
523