xref: /reactos/base/shell/cmd/del.c (revision d5b576b2)
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             res = FilePromptYNA (STRING_DEL_HELP2);
184             if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
185                 return 0x80000000;
186             if (res == PROMPT_ALL)
187                 *dwFlags |= DEL_YES;
188         }
189     }
190 
191     GetFullPathName(szFileName,
192                     MAX_PATH,
193                     szFullPath,
194                     &pFilePart);
195 
196     hFile = FindFirstFile(szFullPath, &f);
197     if (hFile != INVALID_HANDLE_VALUE)
198     {
199         do
200         {
201             bExclusion = FALSE;
202 
203             /*if it is going to be excluded by - no need to check attrs*/
204             if (*dwFlags & DEL_ATTRIBUTES && !bExclusion)
205             {
206                 /*save if file attr check if user doesnt care about that attr anyways*/
207                 if (dwAttrFlags & ATTR_ARCHIVE && !(f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
208                     bExclusion = TRUE;
209                 if (dwAttrFlags & ATTR_HIDDEN && !(f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
210                     bExclusion = TRUE;
211                 if (dwAttrFlags & ATTR_SYSTEM && !(f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
212                     bExclusion = TRUE;
213                 if (dwAttrFlags & ATTR_READ_ONLY && !(f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
214                     bExclusion = TRUE;
215                 if (dwAttrFlags & ATTR_N_ARCHIVE && (f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
216                     bExclusion = TRUE;
217                 if (dwAttrFlags & ATTR_N_HIDDEN && (f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
218                     bExclusion = TRUE;
219                 if (dwAttrFlags & ATTR_N_SYSTEM && (f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
220                     bExclusion = TRUE;
221                 if (dwAttrFlags & ATTR_N_READ_ONLY && (f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
222                     bExclusion = TRUE;
223             }
224             if (bExclusion) continue;
225 
226             /* ignore directories */
227             if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
228 
229             _tcscpy (pFilePart, f.cFileName);
230 
231             /* We cant delete ourselves */
232             if (!_tcscmp (CMDPath,szFullPath)) continue;
233 
234             TRACE("Full filename: %s\n", debugstr_aw(szFullPath));
235 
236             /* ask for deleting */
237             if (*dwFlags & DEL_PROMPT)
238             {
239                 ConErrResPrintf(STRING_DEL_ERROR5, szFullPath);
240 
241                 res = FilePromptYN (STRING_DEL_ERROR6);
242 
243                 if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
244                 {
245                     nErrorLevel = 0;
246                     continue;
247                 }
248             }
249 
250             /*user cant ask it to be quiet and tell you what it did*/
251             if (!(*dwFlags & DEL_QUIET) && !(*dwFlags & DEL_TOTAL))
252             {
253                 ConErrResPrintf(STRING_DEL_ERROR7, szFullPath);
254             }
255 
256             /* delete the file */
257             if (*dwFlags & DEL_NOTHING) continue;
258 
259             if (RemoveFile (szFullPath, *dwFlags, &f))
260             {
261                 dwFiles++;
262             }
263             else
264             {
265                 ErrorMessage (GetLastError(), _T(""));
266 //              FindClose(hFile);
267 //              return -1;
268             }
269         }
270         while (FindNextFile (hFile, &f));
271         FindClose (hFile);
272     }
273     else error_sfile_not_found(szFullPath);
274     return dwFiles;
275 }
276 
277 static DWORD
278 ProcessDirectory(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
279 {
280     TCHAR szFullPath[MAX_PATH];
281     LPTSTR pFilePart;
282     LPTSTR pSearchPart;
283     HANDLE hFile;
284     WIN32_FIND_DATA f;
285     DWORD dwFiles = 0;
286 
287     /* Get the full path to the file */
288     GetFullPathName(FileName,
289                     MAX_PATH,
290                     szFullPath,
291                     &pFilePart);
292 
293     /* Delete all the files in this directory */
294     dwFiles = DeleteFiles(szFullPath, dwFlags, dwAttrFlags);
295     if (dwFiles & 0x80000000)
296         return dwFiles;
297 
298     if (*dwFlags & DEL_SUBDIR)
299     {
300         /* Get just the file name */
301         pSearchPart = _T("*");
302         if (!IsExistingDirectory(szFullPath))
303         {
304             pSearchPart = _tcsrchr(FileName, _T('\\'));
305             if (pSearchPart != NULL)
306                 pSearchPart++;
307             else
308                 pSearchPart = FileName;
309         }
310 
311         /* If no wildcard or file was specified and this is a directory, then
312            display all files in it */
313         if (pFilePart == NULL || IsExistingDirectory(szFullPath))
314         {
315             pFilePart = &szFullPath[_tcslen(szFullPath)];
316             if (*(pFilePart-1) != _T('\\'))
317                 *pFilePart++ = _T('\\');
318             _tcscpy(pFilePart, _T("*"));
319         }
320         else
321         {
322             /* strip the filename off of it */
323             _tcscpy(pFilePart, _T("*"));
324         }
325 
326         /* Enumerate all the sub-directories */
327         hFile = FindFirstFile(szFullPath, &f);
328         if (hFile != INVALID_HANDLE_VALUE)
329         {
330             do
331             {
332                 if (!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
333                         !_tcscmp(f.cFileName, _T(".")) ||
334                         !_tcscmp(f.cFileName, _T("..")))
335                     continue;
336 
337                     _tcscpy(pFilePart, f.cFileName);
338                     _tcscat(pFilePart, _T("\\"));
339                     _tcscat(pFilePart, pSearchPart);
340 
341                     dwFiles +=ProcessDirectory(szFullPath, dwFlags, dwAttrFlags);
342                     if (dwFiles & 0x80000000)
343                     {
344                         break;
345                     }
346             }
347             while (FindNextFile (hFile, &f));
348             FindClose (hFile);
349         }
350     }
351 
352     return dwFiles;
353 }
354 
355 
356 
357 INT CommandDelete (LPTSTR param)
358 {
359     /*cmd is the command that was given, in this case it will always be "del" or "delete"
360     param is whatever is given after the command*/
361 
362     LPTSTR *arg = NULL;
363     INT args;
364     INT i;
365     INT   nEvalArgs = 0; /* number of evaluated arguments */
366     DWORD dwFlags = 0;
367     DWORD dwAttrFlags = 0;
368     DWORD dwFiles = 0;
369     LONG ch;
370     TCHAR szOriginalArg[MAX_PATH];
371 
372     /*checks the first two chars of param to see if it is /?
373     this however allows the following command to not show help
374     "del frog.txt /?" */
375 
376     if (!StringsLoaded)
377     {
378         LoadStrings();
379     }
380 
381     if (!_tcsncmp (param, _T("/?"), 2))
382     {
383         ConOutResPaging(TRUE,STRING_DEL_HELP1);
384         return 0;
385     }
386 
387     nErrorLevel = 0;
388 
389     arg = split (param, &args, FALSE, FALSE);
390 
391     if (args == 0)
392     {
393         /* only command given */
394         error_req_param_missing ();
395         freep (arg);
396         return 1;
397     }
398     /* check for options anywhere in command line */
399     for (i = 0; i < args; i++)
400     {
401         if (*arg[i] == _T('/'))
402         {
403             /*found a command, but check to make sure it has something after it*/
404             if (_tcslen (arg[i]) >= 2)
405             {
406                 ch = _totupper (arg[i][1]);
407                 if (ch == _T('N'))
408                 {
409                     dwFlags |= DEL_NOTHING;
410                 }
411                 else if (ch == _T('P'))
412                 {
413                     dwFlags |= DEL_PROMPT;
414                 }
415                 else if (ch == _T('Q'))
416                 {
417                     dwFlags |= DEL_QUIET;
418                 }
419                 else if (ch == _T('F'))
420                 {
421                     dwFlags |= DEL_FORCE;
422                 }
423                 else if (ch == _T('S'))
424                 {
425                     dwFlags |= DEL_SUBDIR;
426                 }
427                 else if (ch == _T('T'))
428                 {
429                     dwFlags |= DEL_TOTAL;
430                 }
431                 else if (ch == _T('W'))
432                 {
433                     dwFlags |= DEL_WIPE;
434                 }
435                 else if (ch == _T('Y'))
436                 {
437                     dwFlags |= DEL_YES;
438                 }
439                 else if (ch == _T('A'))
440                 {
441                     dwFlags |= DEL_ATTRIBUTES;
442                     /*the proper syntax for /A has a min of 4 chars
443                     i.e. /A:R or /A:-H */
444                     if (_tcslen (arg[i]) < 4)
445                     {
446                         error_invalid_parameter_format(arg[i]);
447                         return 0;
448                     }
449                     ch = _totupper (arg[i][3]);
450                     if (_tcslen (arg[i]) == 4)
451                     {
452                         if (ch == _T('A'))
453                         {
454                             dwAttrFlags |= ATTR_ARCHIVE;
455                         }
456                         if (ch == _T('H'))
457                         {
458                             dwAttrFlags |= ATTR_HIDDEN;
459                         }
460                         if (ch == _T('S'))
461                         {
462                             dwAttrFlags |= ATTR_SYSTEM;
463                         }
464                         if (ch == _T('R'))
465                         {
466                             dwAttrFlags |= ATTR_READ_ONLY;
467                         }
468                     }
469                     if (_tcslen (arg[i]) == 5)
470                     {
471                         if (ch == _T('-'))
472                         {
473                             ch = _totupper (arg[i][4]);
474                             if (ch == _T('A'))
475                             {
476                                 dwAttrFlags |= ATTR_N_ARCHIVE;
477                             }
478                             if (ch == _T('H'))
479                             {
480                                 dwAttrFlags |= ATTR_N_HIDDEN;
481                             }
482                             if (ch == _T('S'))
483                             {
484                                 dwAttrFlags |= ATTR_N_SYSTEM;
485                             }
486                             if (ch == _T('R'))
487                             {
488                                 dwAttrFlags |= ATTR_N_READ_ONLY;
489                             }
490                         }
491                     }
492                 }
493             }
494 
495             nEvalArgs++;
496         }
497     }
498 
499     /* there are only options on the command line --> error!!!
500     there is the same number of args as there is flags, so none of the args were filenames*/
501     if (args == nEvalArgs)
502     {
503         error_req_param_missing ();
504         freep (arg);
505         return 1;
506     }
507 
508     /* keep quiet within batch files */
509     if (bc != NULL) dwFlags |= DEL_QUIET;
510 
511     /* check for filenames anywhere in command line */
512     for (i = 0; i < args && !(dwFiles & 0x80000000); i++)
513     {
514         /*this checks to see if it is a flag; if it isn't, we assume it is a file name*/
515         if ((*arg[i] == _T('/')) || (*arg[i] == _T('-')))
516             continue;
517 
518         /* We want to make a copies of the argument */
519         if (_tcslen(arg[i]) == 2 && arg[i][1] == _T(':'))
520         {
521             /* Check for C: D: ... */
522             GetRootPath(arg[i], szOriginalArg, MAX_PATH);
523         }
524         else
525         {
526             _tcscpy(szOriginalArg,arg[i]);
527         }
528         dwFiles += ProcessDirectory(szOriginalArg, &dwFlags, dwAttrFlags);
529     }
530 
531     freep (arg);
532 
533     /*Based on MS cmd, we only tell what files are being deleted when /S is used */
534     if (dwFlags & DEL_TOTAL)
535     {
536         dwFiles &= 0x7fffffff;
537         if (dwFiles < 2)
538         {
539             ConOutResPrintf(STRING_DEL_HELP3, dwFiles);
540         }
541         else
542         {
543             ConOutResPrintf(STRING_DEL_HELP4, dwFiles);
544         }
545     }
546 
547     return 0;
548 }
549 
550 #endif
551