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
LoadStrings(VOID)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
RemoveFile(LPTSTR lpFileName,DWORD dwFlags,WIN32_FIND_DATA * f)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
DeleteFiles(LPTSTR FileName,DWORD * dwFlags,DWORD dwAttrFlags)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(), NULL);
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
ProcessDirectory(LPTSTR FileName,DWORD * dwFlags,DWORD dwAttrFlags)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
CommandDelete(LPTSTR param)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