[CMD]: Continue refactoring to lay out the way to using the CONUTILS library in CMD...
[reactos.git] / reactos / base / shell / cmd / del.c
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