blob: 07dbe52095a594b62f211a23e15fe619da91fc4e [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 <process.h>
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
struct WatchRootInfo {
char driveLetter;
HANDLE hThread;
HANDLE hStopEvent;
bool bInitialized;
bool bUsed;
bool bFailed;
};
struct WatchRoot {
char *path;
WatchRoot *next;
};
const int ROOT_COUNT = 26;
WatchRootInfo watchRootInfos[ROOT_COUNT];
WatchRoot *firstWatchRoot = NULL;
CRITICAL_SECTION csOutput;
void NormalizeSlashes(char *path, char slash) {
for (char *p = path; *p; p++)
if (*p == '\\' || *p == '/')
*p = slash;
}
// -- Watchable root checks ---------------------------------------------------
bool IsNetworkDrive(const char *name) {
const int BUF_SIZE = 1024;
char buffer[BUF_SIZE];
UNIVERSAL_NAME_INFO *uni = (UNIVERSAL_NAME_INFO *) buffer;
DWORD size = BUF_SIZE;
DWORD result = WNetGetUniversalNameA(
name, // path for network resource
UNIVERSAL_NAME_INFO_LEVEL, // level of information
buffer, // name buffer
&size // size of buffer
);
return result == NO_ERROR;
}
bool IsUnwatchableFS(const char *path) {
char volumeName[MAX_PATH];
char fsName[MAX_PATH];
DWORD fsFlags;
DWORD maxComponentLength;
SetErrorMode(SEM_FAILCRITICALERRORS);
if (!GetVolumeInformationA(path, volumeName, MAX_PATH - 1, NULL, &maxComponentLength, &fsFlags, fsName, MAX_PATH - 1))
return false;
if (strcmp(fsName, "NTFS") && strcmp(fsName, "FAT") && strcmp(fsName, "FAT32") && _stricmp(fsName, "exFAT") && _stricmp(fsName, "reFS"))
return true;
if (!strcmp(fsName, "NTFS") && maxComponentLength != 255 && !(fsFlags & FILE_SUPPORTS_REPARSE_POINTS)) {
// SAMBA reports itself as NTFS
return true;
}
return false;
}
bool IsWatchable(const char *path) {
if (IsNetworkDrive(path))
return false;
if (IsUnwatchableFS(path))
return false;
return true;
}
// -- Substed drive checks ----------------------------------------------------
void PrintRemapForSubstDrive(char driveLetter) {
const int BUF_SIZE = 1024;
char targetPath[BUF_SIZE];
char rootPath[8];
sprintf_s(rootPath, 8, "%c:", driveLetter);
DWORD result = QueryDosDeviceA(rootPath, targetPath, BUF_SIZE);
if (result == 0) {
return;
}
else {
if (targetPath[0] == '\\' && targetPath[1] == '?' && targetPath[2] == '?' && targetPath[3] == '\\') {
// example path: \??\C:\jetbrains\idea
NormalizeSlashes(targetPath, '/');
printf("%c:\n%s\n", driveLetter, targetPath + 4);
}
}
}
void PrintRemapForSubstDrives() {
for (int i = 0; i < ROOT_COUNT; i++) {
if (watchRootInfos[i].bUsed) {
PrintRemapForSubstDrive(watchRootInfos[i].driveLetter);
}
}
}
// -- Mount point enumeration -------------------------------------------------
const int BUFSIZE = 1024;
void PrintDirectoryReparsePoint(const char *path) {
int size = strlen(path) + 2;
char *directory = (char *) malloc(size);
strcpy_s(directory, size, path);
NormalizeSlashes(directory, '\\');
if (directory[strlen(directory) - 1] != '\\')
strcat_s(directory, size, "\\");
char volumeName[_MAX_PATH];
int rc = GetVolumeNameForVolumeMountPointA(directory, volumeName, sizeof(volumeName));
if (rc) {
char volumePathNames[_MAX_PATH];
DWORD returnLength;
rc = GetVolumePathNamesForVolumeNameA(volumeName, volumePathNames, sizeof(volumePathNames), &returnLength);
if (rc) {
char *p = volumePathNames;
while (*p) {
if (_stricmp(p, directory)) // if it's not the path we've already found
{
NormalizeSlashes(directory, '/');
NormalizeSlashes(p, '/');
puts(directory);
puts(p);
}
p += strlen(p) + 1;
}
}
}
free(directory);
}
bool PrintMountPointsForVolume(HANDLE hVol, const char *volumePath, char *Buf) {
HANDLE hPt; // handle for mount point scan
char Path[BUFSIZE]; // string buffer for mount points
DWORD dwSysFlags; // flags that describe the file system
char FileSysNameBuf[BUFSIZE];
// Is this volume NTFS?
GetVolumeInformationA(Buf, NULL, 0, NULL, NULL, &dwSysFlags, FileSysNameBuf, BUFSIZE);
// Detect support for reparse points, and therefore for volume
// mount points, which are implemented using reparse points.
if (!(dwSysFlags & FILE_SUPPORTS_REPARSE_POINTS)) {
return true;
}
// Start processing mount points on this volume.
hPt = FindFirstVolumeMountPointA(
Buf, // root path of volume to be scanned
Path, // pointer to output string
BUFSIZE // size of output buffer
);
// Shall we error out?
if (hPt == INVALID_HANDLE_VALUE) {
return GetLastError() != ERROR_ACCESS_DENIED;
}
// Process the volume mount point.
char *buf = new char[MAX_PATH];
do {
strcpy_s(buf, MAX_PATH, volumePath);
strcat_s(buf, MAX_PATH, Path);
PrintDirectoryReparsePoint(buf);
} while (FindNextVolumeMountPointA(hPt, Path, BUFSIZE));
FindVolumeMountPointClose(hPt);
return true;
}
bool PrintMountPoints(const char *path) {
char volumeUniqueName[128];
BOOL res = GetVolumeNameForVolumeMountPointA(path, volumeUniqueName, 128);
if (!res) {
return false;
}
char buf[BUFSIZE]; // buffer for unique volume identifiers
HANDLE hVol; // handle for the volume scan
// Open a scan for volumes.
hVol = FindFirstVolumeA(buf, BUFSIZE);
// Shall we error out?
if (hVol == INVALID_HANDLE_VALUE) {
return false;
}
bool success = true;
do {
if (!strcmp(buf, volumeUniqueName)) {
success = PrintMountPointsForVolume(hVol, path, buf);
if (!success) break;
}
} while (FindNextVolumeA(hVol, buf, BUFSIZE));
FindVolumeClose(hVol);
return success;
}
// -- Searching for mount points in watch roots (fallback) --------------------
void PrintDirectoryReparsePoints(const char *path) {
char *const buf = _strdup(path);
while (strchr(buf, '/')) {
DWORD attributes = GetFileAttributesA(buf);
if (attributes == INVALID_FILE_ATTRIBUTES)
break;
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
PrintDirectoryReparsePoint(buf);
}
char *pSlash = strrchr(buf, '/');
if (pSlash) {
*pSlash = '\0';
}
}
free(buf);
}
// This is called if we got an ERROR_ACCESS_DENIED when trying to enumerate all mount points for volume.
// In this case, we walk the directory tree up from each watch root, and look at each parent directory
// to check whether it's a reparse point.
void PrintWatchRootReparsePoints() {
WatchRoot *pWatchRoot = firstWatchRoot;
while (pWatchRoot) {
PrintDirectoryReparsePoints(pWatchRoot->path);
pWatchRoot = pWatchRoot->next;
}
}
// -- Watcher thread ----------------------------------------------------------
void PrintChangeInfo(char *rootPath, FILE_NOTIFY_INFORMATION *info) {
char FileNameBuffer[_MAX_PATH];
int converted = WideCharToMultiByte(CP_ACP, 0, info->FileName, info->FileNameLength / sizeof(WCHAR), FileNameBuffer, _MAX_PATH - 1, NULL, NULL);
FileNameBuffer[converted] = '\0';
char *command;
if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
command = "CREATE";
}
else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
command = "DELETE";
}
else if (info->Action == FILE_ACTION_MODIFIED) {
command = "CHANGE";
}
else {
return; // unknown command
}
EnterCriticalSection(&csOutput);
puts(command);
printf("%s", rootPath);
puts(FileNameBuffer);
fflush(stdout);
LeaveCriticalSection(&csOutput);
}
void PrintEverythingChangedUnderRoot(char *rootPath) {
EnterCriticalSection(&csOutput);
puts("RECDIRTY");
puts(rootPath);
fflush(stdout);
LeaveCriticalSection(&csOutput);
}
DWORD WINAPI WatcherThread(void *param) {
WatchRootInfo *info = (WatchRootInfo *) param;
OVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
char rootPath[8];
sprintf_s(rootPath, 8, "%c:\\", info->driveLetter);
HANDLE hRootDir = CreateFileA(rootPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
int buffer_size = 10240;
char *buffer = new char[buffer_size];
HANDLE handles[2];
handles[0] = info->hStopEvent;
handles[1] = overlapped.hEvent;
while (true) {
int rcDir = ReadDirectoryChangesW(hRootDir, buffer, buffer_size, TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL,
&overlapped,
NULL);
if (rcDir == 0) {
info->bFailed = true;
break;
}
int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
if (rc == WAIT_OBJECT_0) {
break;
}
if (rc == WAIT_OBJECT_0 + 1) {
DWORD dwBytesReturned;
if (!GetOverlappedResult(hRootDir, &overlapped, &dwBytesReturned, FALSE)) {
info->bFailed = true;
break;
}
if (dwBytesReturned == 0) {
// don't send dirty too much, everything is changed anyway
if (WaitForSingleObject(info->hStopEvent, 500) == WAIT_OBJECT_0)
break;
// Got a buffer overflow => current changes lost => send RECDIRTY on root
PrintEverythingChangedUnderRoot(rootPath);
} else {
FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
while (true) {
PrintChangeInfo(rootPath, info);
if (!info->NextEntryOffset)
break;
info = (FILE_NOTIFY_INFORMATION *)((char *) info + info->NextEntryOffset);
}
}
}
}
CloseHandle(overlapped.hEvent);
CloseHandle(hRootDir);
delete[] buffer;
return 0;
}
// -- Roots update ------------------------------------------------------------
void MarkAllRootsUnused() {
for (int i = 0; i < ROOT_COUNT; i++) {
watchRootInfos[i].bUsed = false;
}
}
void StartRoot(WatchRootInfo *info) {
info->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
info->hThread = CreateThread(NULL, 0, &WatcherThread, info, 0, NULL);
info->bInitialized = true;
}
void StopRoot(WatchRootInfo *info) {
SetEvent(info->hStopEvent);
WaitForSingleObject(info->hThread, INFINITE);
CloseHandle(info->hThread);
CloseHandle(info->hStopEvent);
info->bInitialized = false;
}
void UpdateRoots() {
char infoBuffer[256];
strcpy_s(infoBuffer, "UNWATCHEABLE\n");
for (int i = 0; i < ROOT_COUNT; i++) {
if (watchRootInfos[i].bInitialized && (!watchRootInfos[i].bUsed || watchRootInfos[i].bFailed)) {
StopRoot(&watchRootInfos[i]);
watchRootInfos[i].bFailed = false;
}
if (watchRootInfos[i].bUsed) {
char rootPath[8];
sprintf_s(rootPath, 8, "%c:\\", watchRootInfos[i].driveLetter);
if (!IsWatchable(rootPath)) {
strcat_s(infoBuffer, rootPath);
strcat_s(infoBuffer, "\n");
continue;
}
if (!watchRootInfos[i].bInitialized) {
StartRoot(&watchRootInfos[i]);
}
}
}
EnterCriticalSection(&csOutput);
fprintf(stdout, "%s", infoBuffer);
puts("#\nREMAP");
PrintRemapForSubstDrives();
bool printedMountPoints = true;
for (int i = 0; i < ROOT_COUNT; i++) {
if (watchRootInfos[i].bUsed) {
char rootPath[8];
sprintf_s(rootPath, 8, "%c:\\", watchRootInfos[i].driveLetter);
if (!PrintMountPoints(rootPath))
printedMountPoints = false;
}
}
if (!printedMountPoints) {
PrintWatchRootReparsePoints();
}
puts("#");
fflush(stdout);
LeaveCriticalSection(&csOutput);
}
void AddWatchRoot(const char *path) {
WatchRoot *watchRoot = (WatchRoot *) malloc(sizeof(WatchRoot));
watchRoot->next = NULL;
watchRoot->path = _strdup(path);
watchRoot->next = firstWatchRoot;
firstWatchRoot = watchRoot;
}
void FreeWatchRootsList() {
WatchRoot *pWatchRoot = firstWatchRoot;
WatchRoot *pNext;
while (pWatchRoot) {
pNext = pWatchRoot->next;
free(pWatchRoot->path);
free(pWatchRoot);
pWatchRoot = pNext;
}
firstWatchRoot = NULL;
}
// -- Main - filewatcher protocol ---------------------------------------------
int _tmain(int argc, _TCHAR *argv[]) {
InitializeCriticalSection(&csOutput);
for (int i = 0; i < 26; i++) {
watchRootInfos[i].driveLetter = 'A' + i;
watchRootInfos[i].bInitialized = false;
watchRootInfos[i].bUsed = false;
}
char buffer[8192];
while (true) {
if (!gets_s(buffer, sizeof(buffer) - 1))
break;
if (!strcmp(buffer, "ROOTS")) {
MarkAllRootsUnused();
FreeWatchRootsList();
bool failed = false;
while (true) {
if (!gets_s(buffer, sizeof(buffer) - 1)) {
failed = true;
break;
}
if (buffer[0] == '#')
break;
int driveLetterPos = 0;
char *pDriveLetter = buffer;
if (*pDriveLetter == '|')
pDriveLetter++;
AddWatchRoot(pDriveLetter);
_strupr_s(buffer, sizeof(buffer) - 1);
char driveLetter = *pDriveLetter;
if (driveLetter >= 'A' && driveLetter <= 'Z') {
watchRootInfos[driveLetter - 'A'].bUsed = true;
}
}
if (failed)
break;
UpdateRoots();
}
if (!strcmp(buffer, "EXIT"))
break;
}
MarkAllRootsUnused();
UpdateRoots();
DeleteCriticalSection(&csOutput);
}