blob: ac7fd65122cfef31700fb1bcacefc8f5a4de207b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <windows.h>
#include <wchar.h>
/*
* cleaner.exe
*
* Deletes a list of files/folders.
*
* The command line syntax is:
*
* arg1: File name containing a list of files/folders to delete.
*
* Requirements for arg1:
* - The arg1 file name MUST be fully qualified. (or be in the same directory
* as the cleaner.exe executable)
* - The file MUST use Windows line ending (CRLF)
* - The file MUST be encoded in UTF-16. The NBI Engine will produce
* this file in Java charset "UNICODE" which effectively means
* UCS-2 BigEndian BOM. Such file will work just fine.
* - Each line in the file is expected to contain a fully qualified file
* or folder name. The entry can be
* - a local file/folder name, e.g. "C:\foo\bar", up to a maximum of 32767
* chars and thus not subject to the original Windows limitation
* of 260 chars for a path name.
* - a UNC, e.g. "\\servername\sharename\foo\bar", subject to a
* restriction of 260 chars.
* - The list MUST be ordered so that the files in a folder are listed before
* the folder itself. (it is not possible to delete a non-empty folder)
*
* Method of working:
*
* 1. After launch the content of command line arg1 is read into memory
* as one big string.
* 2. The string is chopped into a list by separating at the LINE_SEPARATOR.
* 3. Sleep for 2 seconds to allow the launching process (the JVM) to exit.
* 4. Loop over the list of files/folder to delete. Each file-delete operation
* is spawned into a thread of its own up to a maximum of 64 threads.
* Therefore the delete operations happens in parallel rather than in sequence.
* If 64 threads have been spawned then wait for a thread to exit before
* spawning a new one. (therefore never more than 64 threads)
* For each file delete operation do the following:
* - Check to see if the file exists (by getting its attributes)
* - If file: delete file, if directory: delete directory (these are two
* different calls in the Win32 API).
* - Attempt to delete each file/dir up to 15 times sleeping for 200 ms
* between each attempt.
* 5. Wait for all file-delete threads to exit.
* 6. Delete self, i.e. the "cleaner.exe" executable.
* 7. End
*
* The arg1 file is not deleted. However it can be part of the list itself
* if need be.
*
* Author: Dmitry Lipin, 2007
*
*
*
* Changes after transition to Apache:
*
* 14-SEP-2019 Lars Bruun-Hansen (lbruun@apache.org) :
* Function comment headers added.
* Main comment header added.
*
*/
// Retry functionality:
// SLEEP_DELAY : millis between each attempt at a file delete
// MAX_ATTEMPTS : how many times to attempt to delete a file
const DWORD SLEEP_DELAY = 200;
const DWORD MAX_ATTEMPTS = 15;
const DWORD THREAD_FINISHED = 100;
// Number of milliseconds to sleep at launch of the application.
const DWORD INITIAL_DELAY = 2000; // 2 seconds seems to be enough to finish java process
const WCHAR * LINE_SEPARATOR = L"\r\n";
const WCHAR * UNC_PREFIX = L"\\\\?\\"; // Prefix for extended-length path in Win32 API
const WCHAR * UNC_STD_PREFIX = L"\\\\"; // Prefix for UNC paths, for example: \\servername\share\foo\bar
const DWORD UNC_PREFIX_LENGTH = 4;
#ifdef _MSC_VER
#define ZERO(x,y) SecureZeroMemory((x),(y));
#else
#define ZERO(x,y) ZeroMemory((x),(y));
#endif
/*
* Search for the first occurrence of wcs2 within wcs1.
* Returns a pointer to the first occurrence if found.
* If not found, NULL is returned.
*/
WCHAR * search( const WCHAR * wcs1, const WCHAR * wcs2) {
WCHAR *cp = (WCHAR *) wcs1;
WCHAR *s1, *s2;
if ( !*wcs2) {
return (WCHAR *)wcs1;
}
while (*cp) {
s1 = cp;
s2 = (WCHAR *) wcs2;
while ( *s1 && *s2 && !(*s1-*s2) ) {
s1++, s2++;
}
if (!*s2) {
return(cp);
}
cp++;
}
return(NULL);
}
typedef struct _list {
WCHAR * item;
struct _list * next;
} LIST;
WCHAR * toWCHAR(char * charBuffer, DWORD size) {
DWORD i=0;
WCHAR * buffer;
BOOL hasBOM = (*charBuffer == '\xFF' && *(charBuffer+1) == '\xFE');
BOOL hasReverseBOM = (*charBuffer == '\xFE' && *(charBuffer+1) == '\xFF');
char * realStringPtr = charBuffer;
if (hasBOM || hasReverseBOM) {
size-= 2;
realStringPtr+= 2;
if(hasReverseBOM) {
char c;
for (i = 0 ; i < size/2 ; i++) {
c = charBuffer [2 * i] ;
charBuffer [2 * i] = charBuffer [2 * i + 1] ;
charBuffer [2 * i + 1] = c;
}
}
}
buffer = (WCHAR*) LocalAlloc(LPTR, sizeof(WCHAR) * (size/2+1));
ZERO(buffer, sizeof(WCHAR) * (size/2+1));
for(i=0;i<size/2;i++) {
realStringPtr[2*i] = (realStringPtr[2*i]) & 0xFF;
realStringPtr[2*i+1] = (realStringPtr[2*i+1])& 0xFF;
buffer [i] = ((unsigned char)realStringPtr[2*i]) + (((unsigned char)realStringPtr[2*i+1]) << 8);
}
return buffer;
}
/*
* Gets the number of lines in the input.
* (lines are expected to be separated by LINE_SEPARATOR)
*/
DWORD getLinesNumber(WCHAR *str) {
DWORD result = 0;
WCHAR *ptr = str;
WCHAR *ptr2 = str;
DWORD sepLength = lstrlenW(LINE_SEPARATOR);
if(ptr!=NULL) {
while((ptr2 = search(ptr, LINE_SEPARATOR))!=NULL) {
ptr = ptr2 + sepLength;
result++;
if(ptr==NULL) break;
}
if(ptr!=NULL && lstrlenW(ptr) > 0) {
result ++;
}
}
return result;
}
/*
* Produces a string array, 'list', from 'str' by splitting the string
* at each occurrence of LINE_SEPARATOR.
*
* [IN] str: the input
* [OUT] list: string array
* [OUT] number: number of elements in 'list'
*
*/
void getLines(WCHAR *str, WCHAR *** list, DWORD * number) {
WCHAR *ptr = str;
WCHAR *ptr2 = NULL;
DWORD length = 0;
DWORD sepLength = lstrlenW(LINE_SEPARATOR);
DWORD counter = 0;
*number = getLinesNumber(str);
*list = (WCHAR**) LocalAlloc(LPTR, sizeof(WCHAR*) * (*number));
if(ptr!=NULL) {
while(counter < (*number)) {
DWORD i = 0 ;
if((ptr2 = search(ptr, LINE_SEPARATOR))!=NULL) {
ptr2 = search(ptr, LINE_SEPARATOR) + sepLength;
length = lstrlenW(ptr) - lstrlenW(ptr2) - sepLength;
(*list) [counter ] = (WCHAR*) LocalAlloc(LPTR, sizeof(WCHAR*)*(length+1));
ZERO((*list) [counter ], sizeof(WCHAR*)*(length+1));
for(i=0;i<length;i++) {
(*list) [counter ][i]=ptr[i];
}
ptr = ptr2;
} else if((length = lstrlenW(ptr)) > 0) {
(*list)[counter ] = (WCHAR*) LocalAlloc(LPTR, sizeof(WCHAR*)*(length+1));
ZERO((*list) [counter ], sizeof(WCHAR*)*(length+1));
for(i=0;i<length;i++) {
(*list) [counter ][i]=ptr[i];
}
ptr = NULL;
}
counter++;
if(ptr==NULL) break;
}
}
}
/*
* Read file into memory.
*/
void readStringList(HANDLE fileHandle, WCHAR *** list, DWORD *number) {
DWORD size = GetFileSize(fileHandle, NULL); // hope it much less than 2GB
DWORD read = 0;
char * charBuffer = (char*) LocalAlloc(LPTR, sizeof(char) * (size + 2));
ZERO(charBuffer, sizeof(char) * (size + 2));
if(ReadFile(fileHandle, charBuffer, size, &read, 0) && read >=2) {
WCHAR * buffer = toWCHAR(charBuffer, size + 2);
getLines(buffer, list, number);
LocalFree(buffer);
}
LocalFree(charBuffer);
}
void deleteFile(WCHAR * filePath) {
BOOL canDelete = TRUE;
DWORD count = 0 ;
WIN32_FILE_ATTRIBUTE_DATA attrs;
DWORD filePathLength = lstrlenW(filePath);
DWORD prefixLength = (filePath == search(filePath, UNC_STD_PREFIX)) ? 0 : UNC_PREFIX_LENGTH;
DWORD length = filePathLength + prefixLength + 1;
WCHAR * file = (WCHAR*) LocalAlloc(LPTR, sizeof(WCHAR) * length);
DWORD i=0;
for(i=0;i<prefixLength;i++) {
file[i]=UNC_PREFIX[i];
}
for(i=0;i<filePathLength;i++) {
file[i+prefixLength] = filePath[i];
}
// Implementation note:
// GetFileAttributesExW() is used not only to get file attributes
// but also as a way to check if the file/dir (still) exist.
if(GetFileAttributesExW(file, GetFileExInfoStandard, &attrs)) {
if (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { // if read-only attrib is set
if (SetFileAttributesW(file, FILE_ATTRIBUTE_NORMAL) == 0) { // remove read-only attrib
// The read-only attrib could not be deleted. No point in continuing.
canDelete = FALSE;
}
}
if (canDelete) {
if (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
while ((!RemoveDirectoryW(file) || GetFileAttributesExW(file, GetFileExInfoStandard, &attrs)) &&
((count++) < MAX_ATTEMPTS)) {
Sleep(SLEEP_DELAY);
}
} else {
while ((!DeleteFileW(file) || GetFileAttributesExW(file, GetFileExInfoStandard, &attrs)) &&
((count++) < MAX_ATTEMPTS)) {
Sleep(SLEEP_DELAY);
}
}
}
}
LocalFree(file);
}
DWORD WINAPI deleteFileThread(void * ptr) {
WCHAR * file = (WCHAR*) ptr;
deleteFile(file);
return THREAD_FINISHED;
}
void getFreeIndexForNextThread(HANDLE * list, DWORD max, DWORD * counter) {
DWORD code = 0;
DWORD maxReached = 0;
while(1) {
if((*counter)==max) {
maxReached = 1;
*counter = 0;
}
code = 0;
if(list[*counter]==INVALID_HANDLE_VALUE) {
break;
} else if(GetExitCodeThread(list[*counter], &code)!=0 && code==THREAD_FINISHED) {
break;
} else {
*counter = (*counter) + 1;
if((*counter)==max && maxReached == 1) {
*counter = WaitForMultipleObjects(max, list, FALSE, INFINITE) - WAIT_OBJECT_0;
}
}
}
}
/*
* Deletes the the current executable. This is done by spawning a small
* .bat file which does the job. The .bat file even deletes itself when
* finished.
*/
#define BUFSIZE 512
void removeItselfUsingCmd() {
char * currentFile = LocalAlloc(LPTR, sizeof(char) * BUFSIZE);
if (GetModuleFileNameA(0, currentFile, MAX_PATH)) {
char * tempFile = LocalAlloc(LPTR, sizeof(char) * BUFSIZE);
HANDLE hTempFile;
int index = 0;
int i = 0;
char cleanerSuffix [] = ".bat";
for( i = 0; i < (lstrlenA(currentFile) - lstrlenA(cleanerSuffix)); i++) {
tempFile[index++] = currentFile[i];
}
for(i=0;i<lstrlenA(cleanerSuffix);i++) {
tempFile[index++] = cleanerSuffix[i];
}
hTempFile = CreateFileA(tempFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hTempFile != INVALID_HANDLE_VALUE) {
char * command = LocalAlloc(LPTR, sizeof(char) * (lstrlenA(tempFile) + lstrlenA(currentFile) + 6));
DWORD bytesNumber = 0 ;
STARTUPINFO si;
PROCESS_INFORMATION pi;
char * strings [4] = {
":Repeat\n",
"del %1\n",
"if exist %1 goto Repeat\n",
"del %0\n",
};
for(i=0;i<4;i++) {
WriteFile(hTempFile, strings[i], lstrlenA(strings[i]), &bytesNumber, NULL);
}
CloseHandle(hTempFile);
ZERO( &si, sizeof(si) );
si.cb = sizeof(si);
ZERO( &pi, sizeof(pi) );
index=0;
command [index++]= '"';
for(i=0;i<lstrlenA(tempFile);i++) {
command [index++] = tempFile[i];
}
command[index++]= '"';
command[index++]= ' ';
command[index++]= '"';
for(i=0;i<lstrlenA(currentFile);i++) {
command [index++] = currentFile[i];
}
command[index++]= '"';
command[index++]= 0;
CreateProcess(0, command, 0, 0, FALSE, CREATE_NO_WINDOW | IDLE_PRIORITY_CLASS, 0, 0, &si, &pi);
LocalFree(command);
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
LocalFree(tempFile);
}
LocalFree(currentFile);
}
/*
* Changes directory to the directory where the currently executing
* executable is located.
*/
void changeCurrentDirectory() {
WCHAR * currentFile = LocalAlloc(LPTR, sizeof(WCHAR) * MAX_PATH);
if (GetModuleFileNameW(0, currentFile, MAX_PATH)) {
WCHAR * ptr = currentFile;
DWORD i=0;
DWORD len=0;
WCHAR * parent;
while(search(ptr, L"\\")!=NULL) {
ptr = search(ptr, L"\\") + 1;
}
len = lstrlenW(currentFile) - lstrlenW(ptr) - 1;
parent = LocalAlloc(LPTR, sizeof(WCHAR) * (len + 1));
for(i=0;i<len;i++) {
parent[i] = currentFile[i];
}
parent[len] = 0;
SetCurrentDirectoryW(parent);
LocalFree(parent);
}
LocalFree(currentFile);
}
// should be less or equals to MAXIMUM_WAIT_OBJECTS
#define MAXIMUM_THREADS MAXIMUM_WAIT_OBJECTS
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hi, PSTR pszCmdLine, int nCmdShow) {
UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hi);
UNREFERENCED_PARAMETER(pszCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);
int argumentsNumber = 0;
DWORD i=0;
DWORD threadCounter = 0;
DWORD dwThread;
WCHAR ** commandLine = CommandLineToArgvW(GetCommandLineW(), &argumentsNumber);
HANDLE * runningThreads = (HANDLE *) LocalAlloc(LPTR, sizeof(HANDLE) * MAXIMUM_THREADS);
for(i=0;i<MAXIMUM_THREADS;i++) {
runningThreads[i] = INVALID_HANDLE_VALUE;
}
changeCurrentDirectory();
if(argumentsNumber==2) {
WCHAR * filename = commandLine[1];
HANDLE fileList = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, 0);
if(fileList!=0) {
WCHAR ** files = NULL;
DWORD number = 0;
DWORD allThreadsUsed=0;
readStringList(fileList, &files, &number);
CloseHandle(fileList);
if(files!=NULL) {
Sleep(INITIAL_DELAY);
for(i=0;i<number;i++) {
WCHAR * file = files[i];
if(file!=NULL) {
if(lstrlenW(file)>0) {
getFreeIndexForNextThread(runningThreads, MAXIMUM_THREADS, &threadCounter);
runningThreads [threadCounter] = CreateThread( NULL, 0, &deleteFileThread, (LPVOID) file, 0, &dwThread );
threadCounter++;
if(threadCounter==MAXIMUM_THREADS) allThreadsUsed = 1;
}
}
}
WaitForMultipleObjects(allThreadsUsed ? MAXIMUM_THREADS : threadCounter,
runningThreads, TRUE, INFINITE);
for(i=0;i<number;i++) {
if(files[i]!=NULL) LocalFree(files[i]);
}
LocalFree(files);
}
}
}
LocalFree(commandLine);
LocalFree(runningThreads);
//removeItself();
removeItselfUsingCmd();
return 0;
}