some more win32k 64 bit fixes
[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 szDelHelp2[RC_STRING_MAX_SIZE];
81 static TCHAR szDelHelp3[RC_STRING_MAX_SIZE];
82 static TCHAR szDelHelp4[RC_STRING_MAX_SIZE];
83 static TCHAR szDelError5[RC_STRING_MAX_SIZE];
84 static TCHAR szDelError6[RC_STRING_MAX_SIZE];
85 static TCHAR szDelError7[RC_STRING_MAX_SIZE];
86 static TCHAR CMDPath[MAX_PATH];
87
88 static BOOLEAN StringsLoaded = FALSE;
89
90 static VOID LoadStrings(VOID)
91 {
92 LoadString( CMD_ModuleHandle, STRING_DELETE_WIPE, szDeleteWipe, RC_STRING_MAX_SIZE);
93 LoadString( CMD_ModuleHandle, STRING_DEL_HELP2, szDelHelp2, RC_STRING_MAX_SIZE);
94 LoadString( CMD_ModuleHandle, STRING_DEL_HELP3, szDelHelp3, RC_STRING_MAX_SIZE);
95 LoadString( CMD_ModuleHandle, STRING_DEL_HELP4, szDelHelp4, RC_STRING_MAX_SIZE);
96 LoadString( CMD_ModuleHandle, STRING_DEL_ERROR5, szDelError5, RC_STRING_MAX_SIZE);
97 LoadString( CMD_ModuleHandle, STRING_DEL_ERROR6, szDelError6, RC_STRING_MAX_SIZE);
98 LoadString( CMD_ModuleHandle, STRING_DEL_ERROR7, szDelError7, RC_STRING_MAX_SIZE);
99 GetModuleFileName(NULL, CMDPath, MAX_PATH);
100 StringsLoaded = TRUE;
101 }
102
103 static BOOL
104 RemoveFile (LPTSTR lpFileName, DWORD dwFlags, WIN32_FIND_DATA* f)
105 {
106 /*This function is called by CommandDelete and
107 does the actual process of deleting the single
108 file*/
109 if(CheckCtrlBreak(BREAK_INPUT))
110 return 1;
111
112 /*check to see if it is read only and if this is done based on /A
113 if it is done by file name, access is denied. However, if it is done
114 using the /A switch you must un-read only the file and allow it to be
115 deleted*/
116 if((dwFlags & DEL_ATTRIBUTES) || (dwFlags & DEL_FORCE))
117 {
118 if(f->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
119 {
120 /*setting file to normal, not saving old attrs first
121 because the file is going to be deleted anyways
122 so the only thing that matters is that it isnt
123 read only.*/
124 SetFileAttributes(lpFileName,FILE_ATTRIBUTE_NORMAL);
125 }
126 }
127
128 if (dwFlags & DEL_WIPE)
129 {
130
131 HANDLE file;
132 DWORD temp;
133 #define BufferSize 65536
134 BYTE buffer[BufferSize];
135 LONGLONG i;
136 LARGE_INTEGER FileSize;
137
138 FileSize.u.HighPart = f->nFileSizeHigh;
139 FileSize.u.LowPart = f->nFileSizeLow;
140
141 for(i = 0; i < BufferSize; i++)
142 {
143 buffer[i]=rand() % 256;
144 }
145 file = CreateFile (lpFileName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);
146 if (file != INVALID_HANDLE_VALUE)
147 {
148 for(i = 0; i < (FileSize.QuadPart - BufferSize); i += BufferSize)
149 {
150 WriteFile (file, buffer, BufferSize, &temp, NULL);
151 ConOutPrintf (_T("%I64d%% %s\r"),(i * (LONGLONG)100)/FileSize.QuadPart,szDeleteWipe);
152 }
153 WriteFile (file, buffer, (DWORD)(FileSize.QuadPart - i), &temp, NULL);
154 ConOutPrintf (_T("100%% %s\n"),szDeleteWipe);
155 CloseHandle (file);
156 }
157 }
158
159 return DeleteFile (lpFileName);
160 }
161
162
163 static DWORD
164 DeleteFiles(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
165 {
166 TCHAR szFullPath[MAX_PATH];
167 TCHAR szFileName[MAX_PATH];
168 LPTSTR pFilePart;
169 HANDLE hFile;
170 WIN32_FIND_DATA f;
171 BOOL bExclusion;
172 INT res;
173 DWORD dwFiles = 0;
174
175 _tcscpy(szFileName, FileName);
176
177 if(_tcschr (szFileName, _T('*')) == NULL &&
178 IsExistingDirectory (szFileName))
179 {
180 /* If it doesnt have a \ at the end already then on needs to be added */
181 if(szFileName[_tcslen(szFileName) - 1] != _T('\\'))
182 _tcscat (szFileName, _T("\\"));
183 /* Add a wildcard after the \ */
184 _tcscat (szFileName, _T("*"));
185 }
186
187 if(!_tcscmp (szFileName, _T("*")) ||
188 !_tcscmp (szFileName, _T("*.*")) ||
189 (szFileName[_tcslen(szFileName) - 2] == _T('\\') && szFileName[_tcslen(szFileName) - 1] == _T('*')))
190 {
191 /* well, the user wants to delete everything but if they didnt yes DEL_YES, DEL_QUIET, or DEL_PROMPT
192 then we are going to want to make sure that in fact they want to do that. */
193
194 if (!((*dwFlags & DEL_YES) || (*dwFlags & DEL_QUIET) || (*dwFlags & DEL_PROMPT)))
195 {
196 res = FilePromptYNA (szDelHelp2);
197 if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
198 return 0x80000000;
199 if(res == PROMPT_ALL)
200 *dwFlags |= DEL_YES;
201 }
202 }
203
204 GetFullPathName (szFileName,
205 MAX_PATH,
206 szFullPath,
207 &pFilePart);
208
209 hFile = FindFirstFile(szFullPath, &f);
210 if (hFile != INVALID_HANDLE_VALUE)
211 {
212 do
213 {
214 bExclusion = FALSE;
215
216 /*if it is going to be excluded by - no need to check attrs*/
217 if(*dwFlags & DEL_ATTRIBUTES && !bExclusion)
218 {
219
220 /*save if file attr check if user doesnt care about that attr anyways*/
221 if(dwAttrFlags & ATTR_ARCHIVE && !(f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
222 bExclusion = TRUE;
223 if(dwAttrFlags & ATTR_HIDDEN && !(f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
224 bExclusion = TRUE;
225 if(dwAttrFlags & ATTR_SYSTEM && !(f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
226 bExclusion = TRUE;
227 if(dwAttrFlags & ATTR_READ_ONLY && !(f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
228 bExclusion = TRUE;
229 if(dwAttrFlags & ATTR_N_ARCHIVE && (f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
230 bExclusion = TRUE;
231 if(dwAttrFlags & ATTR_N_HIDDEN && (f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
232 bExclusion = TRUE;
233 if(dwAttrFlags & ATTR_N_SYSTEM && (f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
234 bExclusion = TRUE;
235 if(dwAttrFlags & ATTR_N_READ_ONLY && (f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
236 bExclusion = TRUE;
237 }
238
239 if(bExclusion)
240 continue;
241
242 /* ignore directories */
243 if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
244 continue;
245
246
247 _tcscpy (pFilePart, f.cFileName);
248
249 /* We cant delete ourselves */
250 if(!_tcscmp (CMDPath,szFullPath))
251 continue;
252
253
254 TRACE("Full filename: %s\n", debugstr_aw(szFullPath));
255
256 /* ask for deleting */
257 if (*dwFlags & DEL_PROMPT)
258 {
259 ConErrPrintf(szDelError5, szFullPath);
260
261 res = FilePromptYN (szDelError6);
262
263 if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
264 {
265 nErrorLevel = 0;
266 continue;
267 }
268 }
269
270 /*user cant ask it to be quiet and tell you what it did*/
271 if (!(*dwFlags & DEL_QUIET) && !(*dwFlags & DEL_TOTAL))
272 {
273 ConErrPrintf(szDelError7, szFullPath);
274 }
275
276 /* delete the file */
277 if(*dwFlags & DEL_NOTHING)
278 continue;
279
280 if(RemoveFile (szFullPath, *dwFlags, &f))
281 dwFiles++;
282 else
283 {
284 ErrorMessage (GetLastError(), _T(""));
285 // FindClose(hFile);
286 // return -1;
287 }
288 }
289 while (FindNextFile (hFile, &f));
290 FindClose (hFile);
291 }
292 return dwFiles;
293 }
294
295
296 static DWORD
297 ProcessDirectory(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
298 {
299 TCHAR szFullPath[MAX_PATH];
300 LPTSTR pFilePart;
301 LPTSTR pSearchPart;
302 HANDLE hFile;
303 WIN32_FIND_DATA f;
304 DWORD dwFiles = 0;
305
306 GetFullPathName (FileName,
307 MAX_PATH,
308 szFullPath,
309 &pFilePart);
310
311 dwFiles = DeleteFiles(szFullPath, dwFlags, dwAttrFlags);
312 if (dwFiles & 0x80000000)
313 return dwFiles;
314
315 if (*dwFlags & DEL_SUBDIR)
316 {
317 /* Get just the file name */
318 pSearchPart = _tcsrchr(FileName,_T('\\'));
319 if(pSearchPart != NULL)
320 pSearchPart++;
321 else
322 pSearchPart = FileName;
323
324 /* Get the full path to the file */
325 GetFullPathName (FileName,MAX_PATH,szFullPath,NULL);
326
327 /* strip the filename off of it */
328 pFilePart = _tcsrchr(szFullPath, _T('\\'));
329 if (pFilePart == NULL)
330 {
331 pFilePart = szFullPath;
332 }
333 else
334 {
335 pFilePart++;
336 }
337
338 _tcscpy(pFilePart, _T("*"));
339
340 hFile = FindFirstFile(szFullPath, &f);
341 if (hFile != INVALID_HANDLE_VALUE)
342 {
343 do
344 {
345 if (!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
346 !_tcscmp(f.cFileName, _T(".")) ||
347 !_tcscmp(f.cFileName, _T("..")))
348 continue;
349
350 _tcscpy(pFilePart, f.cFileName);
351 _tcscat(pFilePart, _T("\\"));
352 _tcscat(pFilePart, pSearchPart);
353
354 dwFiles +=ProcessDirectory(szFullPath, dwFlags, dwAttrFlags);
355 if (dwFiles & 0x80000000)
356 {
357 break;
358 }
359 }
360 while (FindNextFile (hFile, &f));
361 FindClose (hFile);
362 }
363 }
364 return dwFiles;
365 }
366
367
368
369 INT CommandDelete (LPTSTR cmd, LPTSTR param)
370 {
371 /*cmd is the command that was given, in this case it will always be "del" or "delete"
372 param is whatever is given after the command*/
373
374 LPTSTR *arg = NULL;
375 INT args;
376 INT i;
377 INT nEvalArgs = 0; /* nunber of evaluated arguments */
378 DWORD dwFlags = 0;
379 DWORD dwAttrFlags = 0;
380 DWORD dwFiles = 0;
381 LONG ch;
382 TCHAR szOrginalArg[MAX_PATH];
383
384 /*checks the first two chars of param to see if it is /?
385 this however allows the following command to not show help
386 "del frog.txt /?" */
387
388 if (!StringsLoaded)
389 {
390 LoadStrings();
391 }
392
393 if (!_tcsncmp (param, _T("/?"), 2))
394 {
395 ConOutResPaging(TRUE,STRING_DEL_HELP1);
396 return 0;
397 }
398
399 nErrorLevel = 0;
400
401 arg = split (param, &args, FALSE);
402
403 if (args == 0)
404 {
405 /* only command given */
406 error_req_param_missing ();
407 freep (arg);
408 return 1;
409 }
410 /* check for options anywhere in command line */
411 for (i = 0; i < args; i++)
412 {
413 if (*arg[i] == _T('/'))
414 {
415 /*found a command, but check to make sure it has something after it*/
416 if (_tcslen (arg[i]) >= 2)
417 {
418 ch = _totupper (arg[i][1]);
419 if (ch == _T('N'))
420 {
421 dwFlags |= DEL_NOTHING;
422 }
423 else if (ch == _T('P'))
424 {
425 dwFlags |= DEL_PROMPT;
426 }
427 else if (ch == _T('Q'))
428 {
429 dwFlags |= DEL_QUIET;
430 }
431 else if (ch == _T('F'))
432 {
433 dwFlags |= DEL_FORCE;
434 }
435 else if (ch == _T('S'))
436 {
437 dwFlags |= DEL_SUBDIR;
438 }
439 else if (ch == _T('T'))
440 {
441 dwFlags |= DEL_TOTAL;
442 }
443 else if (ch == _T('W'))
444 {
445 dwFlags |= DEL_WIPE;
446 }
447 else if (ch == _T('Y'))
448 {
449 dwFlags |= DEL_YES;
450 }
451 else if (ch == _T('A'))
452 {
453
454 dwFlags |= DEL_ATTRIBUTES;
455 /*the proper syntax for /A has a min of 4 chars
456 i.e. /A:R or /A:-H */
457 if (_tcslen (arg[i]) < 4)
458 {
459 error_invalid_parameter_format(arg[i]);
460 return 0;
461 }
462 ch = _totupper (arg[i][3]);
463 if (_tcslen (arg[i]) == 4)
464 {
465 if(ch == _T('A'))
466 {
467 dwAttrFlags |= ATTR_ARCHIVE;
468 }
469 if(ch == _T('H'))
470 {
471 dwAttrFlags |= ATTR_HIDDEN;
472 }
473 if(ch == _T('S'))
474 {
475 dwAttrFlags |= ATTR_SYSTEM;
476 }
477 if(ch == _T('R'))
478 {
479 dwAttrFlags |= ATTR_READ_ONLY;
480 }
481 }
482 if (_tcslen (arg[i]) == 5)
483 {
484 if(ch == _T('-'))
485 {
486 ch = _totupper (arg[i][4]);
487 if(ch == _T('A'))
488 {
489 dwAttrFlags |= ATTR_N_ARCHIVE;
490 }
491 if(ch == _T('H'))
492 {
493 dwAttrFlags |= ATTR_N_HIDDEN;
494 }
495 if(ch == _T('S'))
496 {
497 dwAttrFlags |= ATTR_N_SYSTEM;
498 }
499 if(ch == _T('R'))
500 {
501 dwAttrFlags |= ATTR_N_READ_ONLY;
502 }
503 }
504 }
505 }
506 }
507
508 nEvalArgs++;
509 }
510 }
511
512 /* there are only options on the command line --> error!!!
513 there is the same number of args as there is flags, so none of the args were filenames*/
514 if (args == nEvalArgs)
515 {
516 error_req_param_missing ();
517 freep (arg);
518 return 1;
519 }
520
521 /* keep quiet within batch files */
522 if (bc != NULL)
523 dwFlags |= DEL_QUIET;
524
525 /* check for filenames anywhere in command line */
526 for (i = 0; i < args && !(dwFiles & 0x80000000); i++)
527 {
528
529 /*this checks to see if it isnt a flag, if it isnt, we assume it is a file name*/
530 if((*arg[i] == _T('/')) || (*arg[i] == _T('-')))
531 continue;
532
533 /* We want to make a copies of the argument */
534 if(_tcslen(arg[i]) == 2 && arg[i][1] == _T(':'))
535 {
536 /* Check for C: D: ... */
537 GetRootPath(arg[i],szOrginalArg,MAX_PATH);
538 }
539 else
540 {
541 _tcscpy(szOrginalArg,arg[i]);
542 }
543 dwFiles += ProcessDirectory(szOrginalArg, &dwFlags, dwAttrFlags);
544
545 }
546
547 freep (arg);
548
549 /*Based on MS cmd, we only tell what files are being deleted when /S is used */
550 if (dwFlags & DEL_TOTAL)
551 {
552 dwFiles &= 0x7fffffff;
553 if (dwFiles < 2)
554 {
555 ConOutPrintf(szDelHelp3, dwFiles);
556 }
557 else
558 {
559 ConOutPrintf(szDelHelp4, dwFiles);
560 }
561 }
562
563 return 0;
564 }
565
566
567 #endif