xref: /reactos/base/shell/cmd/del.c (revision f2df3bf0)
1 /*
2 *  DEL.C - del internal command.
3 *
4 *
5 *  History:
6 *
7 *    06/29/98 (Rob Lake rlake@cs.mun.ca)
8 *        rewrote del to support wildcards
9 *        added my name to the contributors
10 *
11 *    07/13/98 (Rob Lake)
12 *        fixed bug that caused del not to delete file with out
13 *        attribute. moved set, del, ren, and ver to there own files
14 *
15 *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
16 *        added config.h include
17 *
18 *    09-Dec-1998 (Eric Kohl)
19 *        Fixed command line parsing bugs.
20 *
21 *    21-Jan-1999 (Eric Kohl)
22 *        Started major rewrite using a new structure.
23 *
24 *    03-Feb-1999 (Eric Kohl)
25 *        First working version.
26 *
27 *    30-Mar-1999 (Eric Kohl)
28 *        Added quiet ("/Q"), wipe ("/W") and zap ("/Z") option.
29 *
30 *    06-Nov-1999 (Eric Kohl)
31 *        Little fix to keep DEL quiet inside batch files.
32 *
33 *    28-Jan-2004 (Michael Fritscher <michael@fritscher.net>)
34 *        Added prompt ("/P"), yes ("/Y") and wipe("/W") option.
35 *
36 *    22-Jun-2005 (Brandon Turner <turnerb7@msu.edu>)
37 *        Added exclusive deletion "del * -abc.txt -text*.txt"
38 *
39 *    22-Jun-2005 (Brandon Turner <turnerb7@msu.edu>)
40 *        Implemented /A   example "del /A:H /A:-R *.exe -ping.exe"
41 *
42 *    07-Aug-2005
43 *        Removed the exclusive deletion (see two comments above) because '-' is a valid file name character.
44 *        Optimized the recursive deletion in directories.
45 *        Preload some nice strings.
46 */
47 
48 #include "precomp.h"
49 
50 #ifdef INCLUDE_CMD_DEL
51 
52 
53 enum
54 {
55     DEL_ATTRIBUTES = 0x001,   /* /A */
56     DEL_NOTHING    = 0x004,   /* /N */
57     DEL_PROMPT     = 0x008,   /* /P */
58     DEL_QUIET      = 0x010,   /* /Q */
59     DEL_SUBDIR     = 0x020,   /* /S */
60     DEL_TOTAL      = 0x040,   /* /T */
61     DEL_WIPE       = 0x080,   /* /W */
62     DEL_EMPTYDIR   = 0x100,   /* /X : not implemented */
63     DEL_YES        = 0x200,   /* /Y */
64     DEL_FORCE      = 0x800    /* /F */
65 };
66 
67 enum
68 {
69     ATTR_ARCHIVE     = 0x001,   /* /A:A */
70     ATTR_HIDDEN      = 0x002,   /* /A:H */
71     ATTR_SYSTEM      = 0x004,   /* /A:S */
72     ATTR_READ_ONLY   = 0x008,   /* /A:R */
73     ATTR_N_ARCHIVE   = 0x010,   /* /A:-A */
74     ATTR_N_HIDDEN    = 0x020,   /* /A:-H */
75     ATTR_N_SYSTEM    = 0x040,   /* /A:-S */
76     ATTR_N_READ_ONLY = 0x080    /* /A:-R */
77 };
78 
79 static TCHAR szDeleteWipe[RC_STRING_MAX_SIZE];
80 static TCHAR CMDPath[MAX_PATH];
81 
82 static BOOLEAN StringsLoaded = FALSE;
83 
84 static VOID LoadStrings(VOID)
85 {
86     LoadString(CMD_ModuleHandle, STRING_DELETE_WIPE, szDeleteWipe, ARRAYSIZE(szDeleteWipe));
87     GetModuleFileName(NULL, CMDPath, ARRAYSIZE(CMDPath));
88     StringsLoaded = TRUE;
89 }
90 
91 static BOOL
92 RemoveFile (LPTSTR lpFileName, DWORD dwFlags, WIN32_FIND_DATA* f)
93 {
94     /*This function is called by CommandDelete and
95     does the actual process of deleting the single
96     file*/
97     if (CheckCtrlBreak(BREAK_INPUT))
98         return 1;
99 
100     /*check to see if it is read only and if this is done based on /A
101       if it is done by file name, access is denied. However, if it is done
102       using the /A switch you must un-read only the file and allow it to be
103       deleted*/
104     if ((dwFlags & DEL_ATTRIBUTES) || (dwFlags & DEL_FORCE))
105     {
106         if (f->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
107         {
108             /*setting file to normal, not saving old attrs first
109               because the file is going to be deleted anyways
110               so the only thing that matters is that it isn't
111               read only.*/
112             SetFileAttributes(lpFileName,FILE_ATTRIBUTE_NORMAL);
113         }
114     }
115 
116     if (dwFlags & DEL_WIPE)
117     {
118         HANDLE file;
119         DWORD temp;
120 #define BufferSize 65536
121         BYTE buffer[BufferSize];
122         LONGLONG i;
123         LARGE_INTEGER FileSize;
124 
125         FileSize.u.HighPart = f->nFileSizeHigh;
126         FileSize.u.LowPart = f->nFileSizeLow;
127 
128         for(i = 0; i < BufferSize; i++)
129         {
130             buffer[i]=rand() % 256;
131         }
132         file = CreateFile (lpFileName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,  FILE_FLAG_WRITE_THROUGH, NULL);
133         if (file != INVALID_HANDLE_VALUE)
134         {
135             for(i = 0; i < (FileSize.QuadPart - BufferSize); i += BufferSize)
136             {
137                 WriteFile (file, buffer, BufferSize, &temp, NULL);
138                 ConOutPrintf (_T("%I64d%% %s\r"),(i * (LONGLONG)100)/FileSize.QuadPart,szDeleteWipe);
139             }
140             WriteFile (file, buffer, (DWORD)(FileSize.QuadPart - i), &temp, NULL);
141             ConOutPrintf (_T("100%% %s\n"),szDeleteWipe);
142             CloseHandle (file);
143         }
144     }
145 
146     return DeleteFile (lpFileName);
147 }
148 
149 
150 static DWORD
151 DeleteFiles(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
152 {
153     TCHAR szFullPath[MAX_PATH];
154     TCHAR szFileName[MAX_PATH];
155     LPTSTR pFilePart;
156     HANDLE hFile;
157     WIN32_FIND_DATA f;
158     BOOL bExclusion;
159     INT res;
160     DWORD dwFiles = 0;
161 
162     _tcscpy(szFileName, FileName);
163 
164     if (_tcschr (szFileName, _T('*')) == NULL &&
165         IsExistingDirectory (szFileName))
166     {
167         /* If it doesnt have a \ at the end already then on needs to be added */
168         if (szFileName[_tcslen(szFileName) -  1] != _T('\\'))
169             _tcscat (szFileName, _T("\\"));
170         /* Add a wildcard after the \ */
171         _tcscat (szFileName, _T("*"));
172     }
173 
174     if (!_tcscmp (szFileName, _T("*")) ||
175         !_tcscmp (szFileName, _T("*.*")) ||
176         (szFileName[_tcslen(szFileName) -  2] == _T('\\') && szFileName[_tcslen(szFileName) -  1] == _T('*')))
177     {
178         /* well, the user wants to delete everything but if they didnt yes DEL_YES, DEL_QUIET, or DEL_PROMPT
179            then we are going to want to make sure that in fact they want to do that.  */
180 
181         if (!((*dwFlags & DEL_YES) || (*dwFlags & DEL_QUIET) || (*dwFlags & DEL_PROMPT)))
182         {
183             ConOutPrintf (_T("Delete %s, "),szFileName);
184             res = FilePromptYNA (STRING_DEL_HELP2);
185             if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
186                 return 0x80000000;
187             if (res == PROMPT_ALL)
188                 *dwFlags |= DEL_YES;
189         }
190     }
191 
192     GetFullPathName(szFileName,
193                     MAX_PATH,
194                     szFullPath,
195                     &pFilePart);
196 
197     hFile = FindFirstFile(szFullPath, &f);
198     if (hFile != INVALID_HANDLE_VALUE)
199     {
200         do
201         {
202             bExclusion = FALSE;
203 
204             /*if it is going to be excluded by - no need to check attrs*/
205             if (*dwFlags & DEL_ATTRIBUTES && !bExclusion)
206             {
207                 /*save if file attr check if user doesnt care about that attr anyways*/
208                 if (dwAttrFlags & ATTR_ARCHIVE && !(f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
209                     bExclusion = TRUE;
210                 if (dwAttrFlags & ATTR_HIDDEN && !(f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
211                     bExclusion = TRUE;
212                 if (dwAttrFlags & ATTR_SYSTEM && !(f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
213                     bExclusion = TRUE;
214                 if (dwAttrFlags & ATTR_READ_ONLY && !(f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
215                     bExclusion = TRUE;
216                 if (dwAttrFlags & ATTR_N_ARCHIVE && (f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
217                     bExclusion = TRUE;
218                 if (dwAttrFlags & ATTR_N_HIDDEN && (f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
219                     bExclusion = TRUE;
220                 if (dwAttrFlags & ATTR_N_SYSTEM && (f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
221                     bExclusion = TRUE;
222                 if (dwAttrFlags & ATTR_N_READ_ONLY && (f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
223                     bExclusion = TRUE;
224             }
225             if (bExclusion) continue;
226 
227             /* ignore directories */
228             if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
229 
230             _tcscpy (pFilePart, f.cFileName);
231 
232             /* We cant delete ourselves */
233             if (!_tcscmp (CMDPath,szFullPath)) continue;
234 
235             TRACE("Full filename: %s\n", debugstr_aw(szFullPath));
236 
237             /* ask for deleting */
238             if (*dwFlags & DEL_PROMPT)
239             {
240                 ConErrResPrintf(STRING_DEL_ERROR5, szFullPath);
241 
242                 res = FilePromptYN (STRING_DEL_ERROR6);
243 
244                 if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
245                 {
246                     nErrorLevel = 0;
247                     continue;
248                 }
249             }
250 
251             /*user cant ask it to be quiet and tell you what it did*/
252             if (!(*dwFlags & DEL_QUIET) && !(*dwFlags & DEL_TOTAL))
253             {
254                 ConErrResPrintf(STRING_DEL_ERROR7, szFullPath);
255             }
256 
257             /* delete the file */
258             if (*dwFlags & DEL_NOTHING) continue;
259 
260             if (RemoveFile (szFullPath, *dwFlags, &f))
261             {
262                 dwFiles++;
263             }
264             else
265             {
266                 ErrorMessage (GetLastError(), _T(""));
267 //              FindClose(hFile);
268 //              return -1;
269             }
270         }
271         while (FindNextFile (hFile, &f));
272         FindClose (hFile);
273     }
274     else error_sfile_not_found(szFullPath);
275     return dwFiles;
276 }
277 
278 static DWORD
279 ProcessDirectory(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
280 {
281     TCHAR szFullPath[MAX_PATH];
282     LPTSTR pFilePart;
283     LPTSTR pSearchPart;
284     HANDLE hFile;
285     WIN32_FIND_DATA f;
286     DWORD dwFiles = 0;
287 
288     /* Get the full path to the file */
289     GetFullPathName(FileName,
290                     MAX_PATH,
291                     szFullPath,
292                     &pFilePart);
293 
294     /* Delete all the files in this directory */
295     dwFiles = DeleteFiles(szFullPath, dwFlags, dwAttrFlags);
296     if (dwFiles & 0x80000000)
297         return dwFiles;
298 
299     if (*dwFlags & DEL_SUBDIR)
300     {
301         /* Get just the file name */
302         pSearchPart = _T("*");
303         if (!IsExistingDirectory(szFullPath))
304         {
305             pSearchPart = _tcsrchr(FileName, _T('\\'));
306             if (pSearchPart != NULL)
307                 pSearchPart++;
308             else
309                 pSearchPart = FileName;
310         }
311 
312         /* If no wildcard or file was specified and this is a directory, then
313            display all files in it */
314         if (pFilePart == NULL || IsExistingDirectory(szFullPath))
315         {
316             pFilePart = &szFullPath[_tcslen(szFullPath)];
317             if (*(pFilePart-1) != _T('\\'))
318                 *pFilePart++ = _T('\\');
319             _tcscpy(pFilePart, _T("*"));
320         }
321         else
322         {
323             /* strip the filename off of it */
324             _tcscpy(pFilePart, _T("*"));
325         }
326 
327         /* Enumerate all the sub-directories */
328         hFile = FindFirstFile(szFullPath, &f);
329         if (hFile != INVALID_HANDLE_VALUE)
330         {
331             do
332             {
333                 if (!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
334                         !_tcscmp(f.cFileName, _T(".")) ||
335                         !_tcscmp(f.cFileName, _T("..")))
336                     continue;
337 
338                     _tcscpy(pFilePart, f.cFileName);
339                     _tcscat(pFilePart, _T("\\"));
340                     _tcscat(pFilePart, pSearchPart);
341 
342                     dwFiles +=ProcessDirectory(szFullPath, dwFlags, dwAttrFlags);
343                     if (dwFiles & 0x80000000)
344                     {
345                         break;
346                     }
347             }
348             while (FindNextFile (hFile, &f));
349             FindClose (hFile);
350         }
351     }
352 
353     return dwFiles;
354 }
355 
356 
357 
358 INT CommandDelete (LPTSTR param)
359 {
360     /*cmd is the command that was given, in this case it will always be "del" or "delete"
361     param is whatever is given after the command*/
362 
363     LPTSTR *arg = NULL;
364     INT args;
365     INT i;
366     INT   nEvalArgs = 0; /* number of evaluated arguments */
367     DWORD dwFlags = 0;
368     DWORD dwAttrFlags = 0;
369     DWORD dwFiles = 0;
370     LONG ch;
371     TCHAR szOriginalArg[MAX_PATH];
372 
373     /*checks the first two chars of param to see if it is /?
374     this however allows the following command to not show help
375     "del frog.txt /?" */
376 
377     if (!StringsLoaded)
378     {
379         LoadStrings();
380     }
381 
382     if (!_tcsncmp (param, _T("/?"), 2))
383     {
384         ConOutResPaging(TRUE,STRING_DEL_HELP1);
385         return 0;
386     }
387 
388     nErrorLevel = 0;
389 
390     arg = split (param, &args, FALSE, FALSE);
391 
392     if (args == 0)
393     {
394         /* only command given */
395         error_req_param_missing ();
396         freep (arg);
397         return 1;
398     }
399     /* check for options anywhere in command line */
400     for (i = 0; i < args; i++)
401     {
402         if (*arg[i] == _T('/'))
403         {
404             /*found a command, but check to make sure it has something after it*/
405             if (_tcslen (arg[i]) >= 2)
406             {
407                 ch = _totupper (arg[i][1]);
408                 if (ch == _T('N'))
409                 {
410                     dwFlags |= DEL_NOTHING;
411                 }
412                 else if (ch == _T('P'))
413                 {
414                     dwFlags |= DEL_PROMPT;
415                 }
416                 else if (ch == _T('Q'))
417                 {
418                     dwFlags |= DEL_QUIET;
419                 }
420                 else if (ch == _T('F'))
421                 {
422                     dwFlags |= DEL_FORCE;
423                 }
424                 else if (ch == _T('S'))
425                 {
426                     dwFlags |= DEL_SUBDIR;
427                 }
428                 else if (ch == _T('T'))
429                 {
430                     dwFlags |= DEL_TOTAL;
431                 }
432                 else if (ch == _T('W'))
433                 {
434                     dwFlags |= DEL_WIPE;
435                 }
436                 else if (ch == _T('Y'))
437                 {
438                     dwFlags |= DEL_YES;
439                 }
440                 else if (ch == _T('A'))
441                 {
442                     dwFlags |= DEL_ATTRIBUTES;
443                     /*the proper syntax for /A has a min of 4 chars
444                     i.e. /A:R or /A:-H */
445                     if (_tcslen (arg[i]) < 4)
446                     {
447                         error_invalid_parameter_format(arg[i]);
448                         return 0;
449                     }
450                     ch = _totupper (arg[i][3]);
451                     if (_tcslen (arg[i]) == 4)
452                     {
453                         if (ch == _T('A'))
454                         {
455                             dwAttrFlags |= ATTR_ARCHIVE;
456                         }
457                         if (ch == _T('H'))
458                         {
459                             dwAttrFlags |= ATTR_HIDDEN;
460                         }
461                         if (ch == _T('S'))
462                         {
463                             dwAttrFlags |= ATTR_SYSTEM;
464                         }
465                         if (ch == _T('R'))
466                         {
467                             dwAttrFlags |= ATTR_READ_ONLY;
468                         }
469                     }
470                     if (_tcslen (arg[i]) == 5)
471                     {
472                         if (ch == _T('-'))
473                         {
474                             ch = _totupper (arg[i][4]);
475                             if (ch == _T('A'))
476                             {
477                                 dwAttrFlags |= ATTR_N_ARCHIVE;
478                             }
479                             if (ch == _T('H'))
480                             {
481                                 dwAttrFlags |= ATTR_N_HIDDEN;
482                             }
483                             if (ch == _T('S'))
484                             {
485                                 dwAttrFlags |= ATTR_N_SYSTEM;
486                             }
487                             if (ch == _T('R'))
488                             {
489                                 dwAttrFlags |= ATTR_N_READ_ONLY;
490                             }
491                         }
492                     }
493                 }
494             }
495 
496             nEvalArgs++;
497         }
498     }
499 
500     /* there are only options on the command line --> error!!!
501     there is the same number of args as there is flags, so none of the args were filenames*/
502     if (args == nEvalArgs)
503     {
504         error_req_param_missing ();
505         freep (arg);
506         return 1;
507     }
508 
509     /* keep quiet within batch files */
510     if (bc != NULL) dwFlags |= DEL_QUIET;
511 
512     /* check for filenames anywhere in command line */
513     for (i = 0; i < args && !(dwFiles & 0x80000000); i++)
514     {
515         /*this checks to see if it is a flag; if it isn't, we assume it is a file name*/
516         if ((*arg[i] == _T('/')) || (*arg[i] == _T('-')))
517             continue;
518 
519         /* We want to make a copies of the argument */
520         if (_tcslen(arg[i]) == 2 && arg[i][1] == _T(':'))
521         {
522             /* Check for C: D: ... */
523             GetRootPath(arg[i], szOriginalArg, MAX_PATH);
524         }
525         else
526         {
527             _tcscpy(szOriginalArg,arg[i]);
528         }
529         dwFiles += ProcessDirectory(szOriginalArg, &dwFlags, dwAttrFlags);
530     }
531 
532     freep (arg);
533 
534     /*Based on MS cmd, we only tell what files are being deleted when /S is used */
535     if (dwFlags & DEL_TOTAL)
536     {
537         dwFiles &= 0x7fffffff;
538         if (dwFiles < 2)
539         {
540             ConOutResPrintf(STRING_DEL_HELP3, dwFiles);
541         }
542         else
543         {
544             ConOutResPrintf(STRING_DEL_HELP4, dwFiles);
545         }
546     }
547 
548     return 0;
549 }
550 
551 #endif
552