xref: /reactos/base/shell/cmd/del.c (revision c2c66aff)
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     GetFullPathName(FileName,
288                     MAX_PATH,
289                     szFullPath,
290                     &pFilePart);
291 
292     dwFiles = DeleteFiles(szFullPath, dwFlags, dwAttrFlags);
293     if (dwFiles & 0x80000000) return dwFiles;
294 
295     if (*dwFlags & DEL_SUBDIR)
296     {
297         /* Get just the file name */
298         pSearchPart = _tcsrchr(FileName,_T('\\'));
299         if (pSearchPart != NULL)
300             pSearchPart++;
301         else
302             pSearchPart = FileName;
303 
304         /* Get the full path to the file */
305         GetFullPathName (FileName,MAX_PATH,szFullPath,NULL);
306 
307         /* strip the filename off of it */
308         pFilePart = _tcsrchr(szFullPath, _T('\\'));
309         if (pFilePart == NULL)
310         {
311             pFilePart = szFullPath;
312         }
313         else
314         {
315             pFilePart++;
316         }
317 
318         _tcscpy(pFilePart, _T("*"));
319 
320         hFile = FindFirstFile(szFullPath, &f);
321         if (hFile != INVALID_HANDLE_VALUE)
322         {
323             do
324             {
325                 if (!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
326                         !_tcscmp(f.cFileName, _T(".")) ||
327                         !_tcscmp(f.cFileName, _T("..")))
328                     continue;
329 
330                     _tcscpy(pFilePart, f.cFileName);
331                     _tcscat(pFilePart, _T("\\"));
332                     _tcscat(pFilePart, pSearchPart);
333 
334                     dwFiles +=ProcessDirectory(szFullPath, dwFlags, dwAttrFlags);
335                     if (dwFiles & 0x80000000)
336                     {
337                         break;
338                     }
339             }
340             while (FindNextFile (hFile, &f));
341             FindClose (hFile);
342         }
343     }
344     return dwFiles;
345 }
346 
347 
348 
349 INT CommandDelete (LPTSTR param)
350 {
351     /*cmd is the command that was given, in this case it will always be "del" or "delete"
352     param is whatever is given after the command*/
353 
354     LPTSTR *arg = NULL;
355     INT args;
356     INT i;
357     INT   nEvalArgs = 0; /* number of evaluated arguments */
358     DWORD dwFlags = 0;
359     DWORD dwAttrFlags = 0;
360     DWORD dwFiles = 0;
361     LONG ch;
362     TCHAR szOriginalArg[MAX_PATH];
363 
364     /*checks the first two chars of param to see if it is /?
365     this however allows the following command to not show help
366     "del frog.txt /?" */
367 
368     if (!StringsLoaded)
369     {
370         LoadStrings();
371     }
372 
373     if (!_tcsncmp (param, _T("/?"), 2))
374     {
375         ConOutResPaging(TRUE,STRING_DEL_HELP1);
376         return 0;
377     }
378 
379     nErrorLevel = 0;
380 
381     arg = split (param, &args, FALSE, FALSE);
382 
383     if (args == 0)
384     {
385         /* only command given */
386         error_req_param_missing ();
387         freep (arg);
388         return 1;
389     }
390     /* check for options anywhere in command line */
391     for (i = 0; i < args; i++)
392     {
393         if (*arg[i] == _T('/'))
394         {
395             /*found a command, but check to make sure it has something after it*/
396             if (_tcslen (arg[i]) >= 2)
397             {
398                 ch = _totupper (arg[i][1]);
399                 if (ch == _T('N'))
400                 {
401                     dwFlags |= DEL_NOTHING;
402                 }
403                 else if (ch == _T('P'))
404                 {
405                     dwFlags |= DEL_PROMPT;
406                 }
407                 else if (ch == _T('Q'))
408                 {
409                     dwFlags |= DEL_QUIET;
410                 }
411                 else if (ch == _T('F'))
412                 {
413                     dwFlags |= DEL_FORCE;
414                 }
415                 else if (ch == _T('S'))
416                 {
417                     dwFlags |= DEL_SUBDIR;
418                 }
419                 else if (ch == _T('T'))
420                 {
421                     dwFlags |= DEL_TOTAL;
422                 }
423                 else if (ch == _T('W'))
424                 {
425                     dwFlags |= DEL_WIPE;
426                 }
427                 else if (ch == _T('Y'))
428                 {
429                     dwFlags |= DEL_YES;
430                 }
431                 else if (ch == _T('A'))
432                 {
433                     dwFlags |= DEL_ATTRIBUTES;
434                     /*the proper syntax for /A has a min of 4 chars
435                     i.e. /A:R or /A:-H */
436                     if (_tcslen (arg[i]) < 4)
437                     {
438                         error_invalid_parameter_format(arg[i]);
439                         return 0;
440                     }
441                     ch = _totupper (arg[i][3]);
442                     if (_tcslen (arg[i]) == 4)
443                     {
444                         if (ch == _T('A'))
445                         {
446                             dwAttrFlags |= ATTR_ARCHIVE;
447                         }
448                         if (ch == _T('H'))
449                         {
450                             dwAttrFlags |= ATTR_HIDDEN;
451                         }
452                         if (ch == _T('S'))
453                         {
454                             dwAttrFlags |= ATTR_SYSTEM;
455                         }
456                         if (ch == _T('R'))
457                         {
458                             dwAttrFlags |= ATTR_READ_ONLY;
459                         }
460                     }
461                     if (_tcslen (arg[i]) == 5)
462                     {
463                         if (ch == _T('-'))
464                         {
465                             ch = _totupper (arg[i][4]);
466                             if (ch == _T('A'))
467                             {
468                                 dwAttrFlags |= ATTR_N_ARCHIVE;
469                             }
470                             if (ch == _T('H'))
471                             {
472                                 dwAttrFlags |= ATTR_N_HIDDEN;
473                             }
474                             if (ch == _T('S'))
475                             {
476                                 dwAttrFlags |= ATTR_N_SYSTEM;
477                             }
478                             if (ch == _T('R'))
479                             {
480                                 dwAttrFlags |= ATTR_N_READ_ONLY;
481                             }
482                         }
483                     }
484                 }
485             }
486 
487             nEvalArgs++;
488         }
489     }
490 
491     /* there are only options on the command line --> error!!!
492     there is the same number of args as there is flags, so none of the args were filenames*/
493     if (args == nEvalArgs)
494     {
495         error_req_param_missing ();
496         freep (arg);
497         return 1;
498     }
499 
500     /* keep quiet within batch files */
501     if (bc != NULL) dwFlags |= DEL_QUIET;
502 
503     /* check for filenames anywhere in command line */
504     for (i = 0; i < args && !(dwFiles & 0x80000000); i++)
505     {
506         /*this checks to see if it is a flag; if it isn't, we assume it is a file name*/
507         if ((*arg[i] == _T('/')) || (*arg[i] == _T('-')))
508             continue;
509 
510         /* We want to make a copies of the argument */
511         if (_tcslen(arg[i]) == 2 && arg[i][1] == _T(':'))
512         {
513             /* Check for C: D: ... */
514             GetRootPath(arg[i], szOriginalArg, MAX_PATH);
515         }
516         else
517         {
518             _tcscpy(szOriginalArg,arg[i]);
519         }
520         dwFiles += ProcessDirectory(szOriginalArg, &dwFlags, dwAttrFlags);
521     }
522 
523     freep (arg);
524 
525     /*Based on MS cmd, we only tell what files are being deleted when /S is used */
526     if (dwFlags & DEL_TOTAL)
527     {
528         dwFiles &= 0x7fffffff;
529         if (dwFiles < 2)
530         {
531             ConOutResPrintf(STRING_DEL_HELP3, dwFiles);
532         }
533         else
534         {
535             ConOutResPrintf(STRING_DEL_HELP4, dwFiles);
536         }
537     }
538 
539     return 0;
540 }
541 
542 #endif
543