You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
10 KiB
C++
309 lines
10 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
/* Written in 2011, 2012 by John Ford <jhford@mozilla.com>
|
|
*
|
|
* This program is a replacement for the Posix 'rm' utility implemented as
|
|
* a native Windows win32 application. Build using accompanying Makefile
|
|
* make
|
|
* or by running
|
|
* cl rm.cpp
|
|
*/
|
|
#include <windows.h>
|
|
#include <Strsafe.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
/* TODO:
|
|
* -should the wow64fsredirection stuff be applicable to the whole app
|
|
* or only per empty_directory invocation?
|
|
* -support simple unix-style paths (i.e. map /c/dir1/file1 to c:\\dir1\\file1)
|
|
* -return non-zero if no files are deleted and -f isn't specified
|
|
* -multi-thread deletions
|
|
*/
|
|
|
|
/* This function takes an errNum, filename of the file being operated on and
|
|
* a stdio file handle to the file where output should be printed
|
|
*/
|
|
void print_error(DWORD errNum, wchar_t* filename, FILE* fhandle){
|
|
wchar_t* msg;
|
|
FormatMessageW(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
errNum,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPWSTR) &msg,
|
|
0, NULL);
|
|
fwprintf(fhandle, L"\"%ws\" - %ws", filename, msg);
|
|
}
|
|
|
|
/* Remove an empty directory. This will fail if there are still files or
|
|
* other directories in the directory specified by name
|
|
*/
|
|
BOOL del_directory(wchar_t* name, BOOL force, BOOL verbose, BOOL quiet){
|
|
BOOL rv = TRUE;
|
|
if (verbose) {
|
|
fwprintf(stdout, L"deleting directory \"%ws\"\n", name);
|
|
}
|
|
BOOL delStatus = RemoveDirectoryW(name);
|
|
if (!delStatus) {
|
|
rv = FALSE;
|
|
if (!quiet) {
|
|
print_error(GetLastError(), name, stderr);
|
|
}
|
|
}
|
|
if (verbose) {
|
|
fwprintf(stdout, L"deleted directory \"%ws\"\n", name);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/* Remove a file. If force is true, read only and system file system
|
|
* attributes are cleared before deleting the file
|
|
*/
|
|
BOOL del_file(wchar_t* name, BOOL force, BOOL verbose, BOOL quiet){
|
|
BOOL rv = TRUE;
|
|
if (force) {
|
|
DWORD fileAttr = GetFileAttributesW(name);
|
|
if (fileAttr == INVALID_FILE_ATTRIBUTES) {
|
|
if (!quiet) {
|
|
fwprintf(stderr, L"invalid file attributes for \"%ws\"\n", name);
|
|
}
|
|
// Hmm, should I still try to delete the file?
|
|
return FALSE;
|
|
}
|
|
if (fileAttr & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (!quiet) {
|
|
fwprintf(stderr, L"%ws is a directory, not a file\n", name);
|
|
rv = FALSE;
|
|
}
|
|
}
|
|
// Should really only have one SetFileAttributes
|
|
if (fileAttr & FILE_ATTRIBUTE_SYSTEM ||
|
|
fileAttr & FILE_ATTRIBUTE_READONLY) {
|
|
DWORD toSet = FILE_ATTRIBUTE_NORMAL;
|
|
if (verbose) {
|
|
wprintf(L"changing \"%ws\" file attributes to be removable\n", name);
|
|
}
|
|
DWORD setAttrStatus = SetFileAttributesW(name, toSet);
|
|
if (!setAttrStatus){
|
|
rv = FALSE;
|
|
if (!quiet) {
|
|
print_error(setAttrStatus, name, stderr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (verbose) {
|
|
fwprintf(stdout, L"deleting \"%ws\"\n", name);
|
|
}
|
|
BOOL delStatus = DeleteFileW(name);
|
|
if (!delStatus) {
|
|
rv = FALSE;
|
|
if (!quiet)
|
|
print_error(GetLastError(), name, stderr);
|
|
} else if (verbose) {
|
|
fwprintf(stdout, L"deleted \"%ws\"\n", name);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/* This function will recursively remove all files in a directory
|
|
* then the directory itself.
|
|
*/
|
|
BOOL empty_directory(wchar_t* name, BOOL force, BOOL verbose, BOOL quiet){
|
|
BOOL rv = TRUE;
|
|
DWORD ffStatus;
|
|
WIN32_FIND_DATAW findFileData;
|
|
// TODO: Don't waste so much memory!
|
|
wchar_t dir[MAX_PATH];
|
|
HANDLE hFind = INVALID_HANDLE_VALUE;
|
|
// Used while disabling Wow64 FS Redirection
|
|
//Unused for now PVOID* wow64value = NULL;
|
|
|
|
/* without a trailing \*, the listing for "c:\windows" would show info
|
|
* for "c:\windows", not files *inside* of "c:\windows"
|
|
*/
|
|
StringCchCopyW(dir, MAX_PATH, name); // TODO: Check return
|
|
StringCchCatW(dir, MAX_PATH, L"\\*");
|
|
|
|
/* We don't know what's going on, but Wow64 redirection
|
|
* is not working quite right. Since nothing we have should
|
|
* be in a location that needs Wow64, we should be fine to
|
|
* ignore it
|
|
*/
|
|
//Wow64DisableWow64FsRedirection(wow64value);
|
|
|
|
hFind = FindFirstFileW(dir, &findFileData);
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE) {
|
|
rv = FALSE;
|
|
if (!quiet) {
|
|
print_error(GetLastError(), name, stderr);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
do {
|
|
wchar_t fullName[MAX_PATH];
|
|
StringCchCopyW(fullName, MAX_PATH, name);
|
|
StringCchCatW(fullName, MAX_PATH, L"\\");
|
|
StringCchCatW(fullName, MAX_PATH, findFileData.cFileName);
|
|
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (wcscmp(L".", findFileData.cFileName) != 0 && wcscmp(L"..", findFileData.cFileName) != 0){
|
|
if (!empty_directory(fullName, force, verbose, quiet)){
|
|
rv = FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
if (!del_file(fullName, force, verbose, quiet)) {
|
|
rv = FALSE;
|
|
}
|
|
}
|
|
} while (FindNextFileW(hFind, &findFileData) != 0);
|
|
|
|
/* if (!Wow64RevertWow64FsRedirection(wow64value)) {
|
|
* if (!quiet) {
|
|
* fwprintf(stderr, L"Error restoring Wow64 FS Redirection\n");
|
|
* }
|
|
* return FALSE;
|
|
* }
|
|
*/
|
|
|
|
ffStatus = GetLastError();
|
|
if (ffStatus != ERROR_NO_MORE_FILES) {
|
|
print_error(ffStatus, findFileData.cFileName, stderr);
|
|
rv = FALSE;
|
|
}
|
|
|
|
FindClose(hFind);
|
|
|
|
del_directory(name, force, verbose, quiet);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
/* This function is used to delete a file or directory specified by the
|
|
* 'name' variable. The type of 'name' is figured out. If the recurse
|
|
* option is TRUE, directories will be recursively emptied then deleted.
|
|
* If force is TRUE, file attributes will be changed to allow the program
|
|
* to delete the file. The verbose option will cause non-fatal error messages
|
|
* to print to stderr. The quiet option will suppress all but fatal
|
|
* error messages
|
|
*/
|
|
BOOL del(wchar_t* name, BOOL recurse, BOOL force, BOOL verbose, BOOL quiet) {
|
|
BOOL rv = TRUE;
|
|
DWORD fileAttr = GetFileAttributesW(name);
|
|
if (fileAttr == INVALID_FILE_ATTRIBUTES){
|
|
rv = FALSE;
|
|
if (!quiet) {
|
|
fwprintf(stderr, L"Invalid file attributes for \"%ws\"\n", name);
|
|
}
|
|
} else if (fileAttr & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (recurse){
|
|
if (!empty_directory(name, force, verbose, quiet)){
|
|
rv = FALSE;
|
|
}
|
|
} else {
|
|
if (!del_directory(name, force, verbose, quiet)){
|
|
rv = FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
if (!del_file(name, force, verbose, quiet)){
|
|
rv = FALSE;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/* This struct is used by the command line parser */
|
|
struct node{
|
|
node *next;
|
|
wchar_t* data;
|
|
};
|
|
|
|
int wmain(int argc, wchar_t** argv)
|
|
{
|
|
int exitCode = 0;
|
|
int i, j;
|
|
BOOL verbose = FALSE, force = FALSE, quiet = FALSE, recurse = FALSE;
|
|
BOOL onlyFiles = FALSE;
|
|
struct node *previous = NULL;
|
|
struct node *start = NULL;
|
|
for (i = 1 ; i < argc ; i++) {
|
|
if (wcscmp(argv[i], L"--") == 0) {
|
|
/* Once we've seen '--' as an arg in the argv,
|
|
* we want to interpret everything after that point
|
|
* as a file
|
|
*/
|
|
onlyFiles = TRUE;
|
|
} else if (!onlyFiles && argv[i][0] == L'-') {
|
|
/* Before the -- appears (if ever), we assume that all
|
|
* args starting with - are options. If I wanted to do
|
|
* full words, I would have a check for the second char
|
|
* being another - in a case and use that case and wsccmp
|
|
* to set the options.
|
|
*/
|
|
for (j = 1 ; j < wcslen(argv[i]) ; j++) {
|
|
switch(argv[i][j]){
|
|
case L'v':
|
|
verbose = TRUE;
|
|
break;
|
|
case L'q':
|
|
quiet = TRUE;
|
|
break;
|
|
case L'r':
|
|
recurse = TRUE;
|
|
break;
|
|
case L'f':
|
|
force = TRUE;
|
|
break;
|
|
default:
|
|
fwprintf(stderr, L"The option -%wc is not valid\n", argv[i][j]);
|
|
exitCode = 1;
|
|
}
|
|
}
|
|
} else {
|
|
/* If there are no more options, or we are forcing the rest of the
|
|
* args to be files, we add them to the linked list. This list stores
|
|
* args in reverse order to what is on the command line.
|
|
*/
|
|
struct node *nextNode = (struct node *) malloc(sizeof(struct node));
|
|
nextNode->data = argv[i];
|
|
nextNode->next = previous;
|
|
previous = nextNode;
|
|
start = nextNode;
|
|
}
|
|
}
|
|
if (verbose && quiet) {
|
|
fwprintf(stderr, L"The -q (quiet) and -v (verbose) options are incompatible\n");
|
|
exitCode = 1;
|
|
}
|
|
/* If everything is good, its time to start deleting the files.
|
|
* We do this by traversing the linked list, deleting the current
|
|
* node then deleting the current node before moving to the next
|
|
*/
|
|
if (!exitCode) {
|
|
struct node* current = start;
|
|
while (current != NULL){
|
|
BOOL result = del(current->data, recurse, force, verbose, quiet);
|
|
if (!result) {
|
|
exitCode = 1;
|
|
}
|
|
struct node* cleanup = current;
|
|
current = current->next;
|
|
free(cleanup);
|
|
}
|
|
}
|
|
|
|
return exitCode;
|
|
}
|