mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
FileWatcher refactoring
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* @author max
|
||||
*/
|
||||
package com.intellij.openapi.vfs.watcher;
|
||||
|
||||
public enum ChangeKind {
|
||||
CREATE,
|
||||
DELETE,
|
||||
STATS,
|
||||
CHANGE,
|
||||
DIRTY,
|
||||
RECDIRTY,
|
||||
RESET
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import com.intellij.ide.impl.DataManagerImpl;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.vfs.local.win32.FileWatcher;
|
||||
import com.intellij.openapi.vfs.impl.local.FileWatcher;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* @author max
|
||||
*/
|
||||
package com.intellij.openapi.vfs.impl.local;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.newvfs.ManagingFS;
|
||||
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
|
||||
import com.intellij.openapi.vfs.watcher.ChangeKind;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class FileWatcher {
|
||||
@NonNls public static final String PROPERTY_WATCHER_DISABLED = "filewatcher.disabled";
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.FileWatcher");
|
||||
|
||||
@NonNls private static final String GIVEUP_COMMAND = "GIVEUP";
|
||||
@NonNls private static final String RESET_COMMAND = "RESET";
|
||||
@NonNls private static final String UNWATCHEABLE_COMMAND = "UNWATCHEABLE";
|
||||
@NonNls private static final String ROOTS_COMMAND = "ROOTS";
|
||||
@NonNls private static final String EXIT_COMMAND = "EXIT";
|
||||
|
||||
private final Object LOCK = new Object();
|
||||
private List<String> myDirtyPaths = new ArrayList<String>();
|
||||
private List<String> myDirtyRecursivePaths = new ArrayList<String>();
|
||||
private List<String> myDirtyDirs = new ArrayList<String>();
|
||||
private List<String> myManualWatchRoots = new ArrayList<String>();
|
||||
|
||||
private List<String> myRecursiveWatchRoots = new ArrayList<String>();
|
||||
private List<String> myFlatWatchRoots = new ArrayList<String>();
|
||||
|
||||
private Process notifierProcess;
|
||||
private BufferedReader notifierReader;
|
||||
private BufferedWriter notifierWriter;
|
||||
|
||||
private static FileWatcher ourInstance = new FileWatcher();
|
||||
private int attemptCount = 0;
|
||||
private static final int MAX_PROCESS_LAUNCH_ATTEMPT_COUNT = 10;
|
||||
private boolean isShuttingDown = false;
|
||||
|
||||
public static FileWatcher getInstance() {
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
private FileWatcher() {
|
||||
try {
|
||||
if (!"true".equals(System.getProperty(PROPERTY_WATCHER_DISABLED))) {
|
||||
startupProcess();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (notifierProcess != null) {
|
||||
LOG.info("Native file watcher is operational.");
|
||||
new WatchForChangesThread().start();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
isShuttingDown = true;
|
||||
shutdownProcess();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
LOG.info("Native file watcher failed to startup.");
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getDirtyPaths() {
|
||||
synchronized (LOCK) {
|
||||
final List<String> result = myDirtyPaths;
|
||||
myDirtyPaths = new ArrayList<String>();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getDirtyRecursivePaths() {
|
||||
synchronized (LOCK) {
|
||||
final List<String> result = myDirtyRecursivePaths;
|
||||
myDirtyRecursivePaths = new ArrayList<String>();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<String> getDirtyDirs() {
|
||||
synchronized (LOCK) {
|
||||
final List<String> result = myDirtyDirs;
|
||||
myDirtyDirs = new ArrayList<String>();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getManualWatchRoots() {
|
||||
synchronized (LOCK) {
|
||||
return Collections.unmodifiableList(myManualWatchRoots);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWatchRoots(List<String> recursive, List<String> flat) {
|
||||
synchronized (LOCK) {
|
||||
try {
|
||||
if (myRecursiveWatchRoots.equals(recursive) && myFlatWatchRoots.equals(flat)) return;
|
||||
|
||||
myRecursiveWatchRoots = recursive;
|
||||
myFlatWatchRoots = flat;
|
||||
|
||||
writeLine(ROOTS_COMMAND);
|
||||
for (String path : recursive) {
|
||||
writeLine(path);
|
||||
}
|
||||
for (String path : flat) {
|
||||
writeLine("|" + path);
|
||||
}
|
||||
writeLine("#");
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setManualWatchRoots(List<String> roots) {
|
||||
synchronized (LOCK) {
|
||||
myManualWatchRoots = roots;
|
||||
}
|
||||
}
|
||||
|
||||
private void startupProcess() throws IOException {
|
||||
if (isShuttingDown) return;
|
||||
|
||||
if (attemptCount++ > MAX_PROCESS_LAUNCH_ATTEMPT_COUNT) {
|
||||
throw new IOException("Can't launch process anymore");
|
||||
}
|
||||
|
||||
shutdownProcess();
|
||||
|
||||
notifierProcess = Runtime.getRuntime().exec(new String[]{PathManager.getBinPath() + "/fsnotifier"});
|
||||
notifierReader = new BufferedReader(new InputStreamReader(notifierProcess.getInputStream()));
|
||||
notifierWriter = new BufferedWriter(new OutputStreamWriter(notifierProcess.getOutputStream()));
|
||||
}
|
||||
|
||||
private void shutdownProcess() throws IOException {
|
||||
if (notifierProcess != null) {
|
||||
writeLine(EXIT_COMMAND);
|
||||
|
||||
notifierProcess = null;
|
||||
notifierReader = null;
|
||||
notifierWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOperational() {
|
||||
return notifierProcess != null;
|
||||
}
|
||||
|
||||
private class WatchForChangesThread extends Thread {
|
||||
|
||||
public WatchForChangesThread() {
|
||||
//noinspection HardCodedStringLiteral
|
||||
super("WatchForChangesThread");
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
if (ApplicationManager.getApplication().isDisposeInProgress() || notifierProcess == null || isShuttingDown) return;
|
||||
|
||||
final String command = readLine();
|
||||
if (command == null) {
|
||||
// Unexpected process exit, relaunch attempt
|
||||
startupProcess();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GIVEUP_COMMAND.equals(command)) {
|
||||
shutdownProcess();
|
||||
return;
|
||||
}
|
||||
if (RESET_COMMAND.equals(command)) {
|
||||
reset();
|
||||
}
|
||||
else if (UNWATCHEABLE_COMMAND.equals(command)) {
|
||||
List<String> roots = new ArrayList<String>();
|
||||
do {
|
||||
final String path = readLine();
|
||||
if (path == null || "#".equals(path)) break;
|
||||
roots.add(path);
|
||||
}
|
||||
while (true);
|
||||
|
||||
setManualWatchRoots(roots);
|
||||
}
|
||||
else {
|
||||
String path = readLine();
|
||||
if (path == null) {
|
||||
// Unexpected process exit, relaunch attempt
|
||||
startupProcess();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isWatcheable(path)) {
|
||||
try {
|
||||
onPathChange(ChangeKind.valueOf(command), path);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
LOG.error("Illegal watcher command: " + command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info("Watcher terminated and attempt to restart has failed. Exiting watching thread.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLine(String line) throws IOException {
|
||||
notifierWriter.write(line);
|
||||
notifierWriter.newLine();
|
||||
notifierWriter.flush();
|
||||
}
|
||||
|
||||
private String readLine() throws IOException {
|
||||
return notifierReader.readLine();
|
||||
}
|
||||
|
||||
private boolean isWatcheable(final String path) {
|
||||
if (path == null) return false;
|
||||
|
||||
synchronized (LOCK) {
|
||||
for (String root : myRecursiveWatchRoots) {
|
||||
if (FileUtil.startsWith(path, root)) return true;
|
||||
}
|
||||
|
||||
for (String root : myFlatWatchRoots) {
|
||||
if (FileUtil.pathsEqual(path, root)) return true;
|
||||
if (FileUtil.pathsEqual(new File(path).getParentFile().getPath(), root)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onPathChange(final ChangeKind changeKind, final String path) {
|
||||
synchronized (LOCK) {
|
||||
switch (changeKind) {
|
||||
case STATS:
|
||||
case CHANGE:
|
||||
myDirtyPaths.add(path);
|
||||
break;
|
||||
|
||||
case CREATE:
|
||||
case DELETE:
|
||||
myDirtyPaths.add(new File(path).getParentFile().getPath());
|
||||
break;
|
||||
|
||||
case DIRTY:
|
||||
myDirtyDirs.add(path);
|
||||
break;
|
||||
|
||||
case RECDIRTY:
|
||||
myDirtyRecursivePaths.add(path);
|
||||
break;
|
||||
|
||||
case RESET:
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
synchronized (LOCK) {
|
||||
myDirtyPaths.clear();
|
||||
myDirtyDirs.clear();
|
||||
myDirtyRecursivePaths.clear();
|
||||
|
||||
for (VirtualFile root : ManagingFS.getInstance().getLocalRoots()) {
|
||||
((NewVirtualFile)root).markDirtyRecursively();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import com.intellij.util.concurrency.JBReentrantReadWriteLock;
|
||||
import com.intellij.util.concurrency.LockFactory;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.intellij.util.io.fs.IFile;
|
||||
import com.intellij.vfs.local.win32.FileWatcher;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -41,11 +40,8 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
private final List<WatchRequest> myRootsToWatch = new ArrayList<WatchRequest>();
|
||||
private WatchRequest[] myCachedNormalizedRequests = null;
|
||||
|
||||
private final Set<String> myDirtyFiles = new HashSet<String>(); // dirty files when FileWatcher is available
|
||||
private final Set<String> myDeletedFiles = new HashSet<String>();
|
||||
|
||||
private final List<LocalFileOperationsHandler> myHandlers = new ArrayList<LocalFileOperationsHandler>();
|
||||
private List<String> myManualWatchRoots = new ArrayList<String>();
|
||||
private final FileWatcher myWatcher;
|
||||
|
||||
private static class WatchRequestImpl implements WatchRequest {
|
||||
public final String myRootPath;
|
||||
@@ -109,9 +105,8 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
}
|
||||
|
||||
public LocalFileSystemImpl() {
|
||||
if (FileWatcher.isAvailable()) {
|
||||
FileWatcher.initialize();
|
||||
new WatchForChangesThread().start();
|
||||
myWatcher = FileWatcher.getInstance();
|
||||
if (myWatcher.isOperational()) {
|
||||
new StoreRefreshStatusThread().start();
|
||||
}
|
||||
}
|
||||
@@ -141,20 +136,12 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
}
|
||||
|
||||
myRootsToWatch.clear();
|
||||
myDirtyFiles.clear();
|
||||
myDeletedFiles.clear();
|
||||
|
||||
final File file = new File(FileUtil.getTempDirectory());
|
||||
String path = file.getCanonicalPath().replace(File.separatorChar, '/');
|
||||
addRootToWatch(path, true);
|
||||
}
|
||||
|
||||
private static void updateFileWatcher() {
|
||||
if (FileWatcher.isAvailable()) {
|
||||
FileWatcher.interruptWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
@@ -291,21 +278,15 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
}
|
||||
|
||||
private void storeRefreshStatusToFiles() {
|
||||
if (FileWatcher.isAvailable()) {
|
||||
markPathsDirty(getAndClear(myDirtyFiles));
|
||||
markPathsDirty(getAndClear(myDeletedFiles));
|
||||
if (FileWatcher.getInstance().isOperational()) {
|
||||
// TODO: different ways to marky dirty for all these cases
|
||||
markPathsDirty(FileWatcher.getInstance().getDirtyPaths());
|
||||
markFlatDirsDirty(FileWatcher.getInstance().getDirtyDirs());
|
||||
markRecursiveDirsDirty(FileWatcher.getInstance().getDirtyRecursivePaths());
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] getAndClear(final Set<String> set) {
|
||||
synchronized (set) {
|
||||
final String[] copy = set.toArray(new String[set.size()]);
|
||||
set.clear();
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
private void markPathsDirty(final String[] dirtyFiles) {
|
||||
private void markPathsDirty(final List<String> dirtyFiles) {
|
||||
for (String dirtyFile : dirtyFiles) {
|
||||
String path = dirtyFile.replace(File.separatorChar, '/');
|
||||
VirtualFile file = findFileByPathIfCached(path);
|
||||
@@ -315,11 +296,35 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
}
|
||||
}
|
||||
|
||||
private void markFlatDirsDirty(final List<String> dirtyFiles) {
|
||||
for (String dirtyFile : dirtyFiles) {
|
||||
String path = dirtyFile.replace(File.separatorChar, '/');
|
||||
VirtualFile file = findFileByPathIfCached(path);
|
||||
if (file instanceof NewVirtualFile) {
|
||||
final NewVirtualFile nvf = (NewVirtualFile)file;
|
||||
nvf.markDirty();
|
||||
for (VirtualFile child : nvf.getCachedChildren()) {
|
||||
((NewVirtualFile)child).markDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markRecursiveDirsDirty(final List<String> dirtyFiles) {
|
||||
for (String dirtyFile : dirtyFiles) {
|
||||
String path = dirtyFile.replace(File.separatorChar, '/');
|
||||
VirtualFile file = findFileByPathIfCached(path);
|
||||
if (file instanceof NewVirtualFile) {
|
||||
((NewVirtualFile)file).markDirtyRecursively();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void markSuspicousFilesDirty(List<VirtualFile> files) {
|
||||
storeRefreshStatusToFiles();
|
||||
|
||||
if (FileWatcher.isAvailable()) {
|
||||
for (String root : myManualWatchRoots) {
|
||||
if (myWatcher.isOperational()) {
|
||||
for (String root : myWatcher.getManualWatchRoots()) {
|
||||
final VirtualFile suspicousRoot = findFileByPathIfCached(root);
|
||||
if (suspicousRoot != null) {
|
||||
((NewVirtualFile)suspicousRoot).markDirtyRecursively();
|
||||
@@ -371,84 +376,30 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
return path.replace(File.separatorChar, '/');
|
||||
}
|
||||
|
||||
private class WatchForChangesThread extends Thread {
|
||||
public WatchForChangesThread() {
|
||||
//noinspection HardCodedStringLiteral
|
||||
super("WatchForChangesThread");
|
||||
}
|
||||
|
||||
public void run() {
|
||||
updateFileWatcher();
|
||||
try {
|
||||
while (true) {
|
||||
if (ApplicationManager.getApplication().isDisposeInProgress()) return;
|
||||
|
||||
FileWatcher.ChangeInfo[] infos = FileWatcher.waitForChange();
|
||||
|
||||
if (infos == null) {
|
||||
setUpFileWatcher();
|
||||
}
|
||||
else {
|
||||
for (FileWatcher.ChangeInfo info : infos) {
|
||||
if (info == null) continue;
|
||||
|
||||
String path = info.getFilePath();
|
||||
int changeType = info.getChangeType();
|
||||
if (changeType == FileWatcher.FILE_MODIFIED) {
|
||||
synchronized (myDirtyFiles) {
|
||||
myDirtyFiles.add(path);
|
||||
}
|
||||
}
|
||||
else if (changeType == FileWatcher.FILE_ADDED || changeType == FileWatcher.FILE_RENAMED_NEW_NAME) {
|
||||
synchronized (myDirtyFiles) {
|
||||
String parent = new File(path).getParent();
|
||||
if (parent != null) {
|
||||
myDirtyFiles.add(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (changeType == FileWatcher.FILE_REMOVED || changeType == FileWatcher.FILE_RENAMED_OLD_NAME) {
|
||||
synchronized (myDeletedFiles) {
|
||||
myDeletedFiles.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info("Watcher terminated and attempt to restart has failed. Exiting watching thread.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setUpFileWatcher() {
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
|
||||
if (application.isDisposeInProgress()) return;
|
||||
|
||||
if (FileWatcher.isAvailable()) {
|
||||
if (myWatcher.isOperational()) {
|
||||
application.runReadAction(new Runnable() {
|
||||
public void run() {
|
||||
WRITE_LOCK.lock();
|
||||
try {
|
||||
final WatchRequest[] watchRequests = normalizeRootsForRefresh();
|
||||
String[] dirPaths = new String[watchRequests.length];
|
||||
boolean[] toWatchRecursively = new boolean[watchRequests.length];
|
||||
int cnt = 0;
|
||||
List<String> myRecursiveRoots = new ArrayList<String>();
|
||||
List<String> myFlatRoots = new ArrayList<String>();
|
||||
|
||||
for (WatchRequest root : watchRequests) {
|
||||
dirPaths[cnt] = root.getFileSystemRootPath();
|
||||
toWatchRecursively[cnt] = root.isToWatchRecursively();
|
||||
cnt++;
|
||||
if (root.isToWatchRecursively()) {
|
||||
myRecursiveRoots.add(root.getFileSystemRootPath());
|
||||
}
|
||||
else {
|
||||
myFlatRoots.add(root.getFileSystemRootPath());
|
||||
}
|
||||
}
|
||||
|
||||
final Vector<String> watchManual = new Vector<String>();
|
||||
FileWatcher.setup(dirPaths, toWatchRecursively, watchManual);
|
||||
|
||||
myManualWatchRoots = new ArrayList<String>();
|
||||
for (int i = 0; i < watchManual.size(); i++) {
|
||||
myManualWatchRoots.add(watchManual.elementAt(i));
|
||||
}
|
||||
myWatcher.setWatchRoots(myRecursiveRoots, myFlatRoots);
|
||||
}
|
||||
finally {
|
||||
WRITE_LOCK.unlock();
|
||||
@@ -469,7 +420,6 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
}
|
||||
|
||||
public void run() {
|
||||
//noinspection InfiniteLoopStatement
|
||||
while (true) {
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
if (application == null || application.isDisposed()) break;
|
||||
@@ -897,7 +847,7 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
|
||||
}
|
||||
};
|
||||
|
||||
if (asynchronous && FileWatcher.isAvailable()) {
|
||||
if (asynchronous && myWatcher.isOperational()) {
|
||||
RefreshQueue.getInstance().refresh(true, true, heavyRefresh, ManagingFS.getInstance().getRoots(this));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -30,11 +30,6 @@
|
||||
<implementation-class>com.intellij.openapi.vfs.newvfs.persistent.PersistentFS</implementation-class>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<interface-class>com.intellij.openapi.vfs.watcher.FileSystemTracker</interface-class>
|
||||
<implementation-class>com.intellij.openapi.vfs.impl.watcher.FileSystemTrackerImpl</implementation-class>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<interface-class>com.intellij.openapi.vfs.pointers.VirtualFilePointerManager</interface-class>
|
||||
<implementation-class>com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl</implementation-class>
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
static int ReportMountedFileSystems()
|
||||
// If fsBuf is too small to account for all volumes, getfsstat will
|
||||
// silently truncate the returned information. Worse yet, it returns
|
||||
// the number of volumes it passed back, not the number of volumes present,
|
||||
// so you can't tell if the list was truncated.
|
||||
//
|
||||
// So, in order to get an accurate snapshot of the volume list, I call
|
||||
// getfsstat with a NULL fsBuf to get a count (fsCountOrig), then allocate a
|
||||
// buffer that holds (fsCountOrig + 1) items, then call getfsstat again with
|
||||
// that buffer. If the list was silently truncated, the second count (fsCount)
|
||||
// will be (fsCountOrig + 1), and we loop to try again.
|
||||
{
|
||||
int err;
|
||||
int fsCountOrig;
|
||||
int fsCount;
|
||||
struct statfs * fsBuf;
|
||||
bool done;
|
||||
|
||||
|
||||
fsBuf = NULL;
|
||||
fsCount = 0;
|
||||
|
||||
done = false;
|
||||
do {
|
||||
// Get the initial count.
|
||||
err = 0;
|
||||
fsCountOrig = getfsstat(NULL, 0, MNT_WAIT);
|
||||
if (fsCountOrig < 0) {
|
||||
err = errno;
|
||||
}
|
||||
|
||||
// Allocate a buffer for fsCountOrig + 1 items.
|
||||
if (err == 0) {
|
||||
if (fsBuf != NULL) {
|
||||
free(fsBuf);
|
||||
}
|
||||
fsBuf = malloc((fsCountOrig + 1) * sizeof(*fsBuf));
|
||||
if (fsBuf == NULL) {
|
||||
err = ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list.
|
||||
if (err == 0) {
|
||||
fsCount = getfsstat(fsBuf, (int) ((fsCountOrig + 1) * sizeof(*fsBuf)), MNT_WAIT);
|
||||
if (fsCount < 0) {
|
||||
err = errno;
|
||||
}
|
||||
}
|
||||
|
||||
// We got the full list if the number of items returned by the kernel
|
||||
// is strictly less than the buffer that we allocated (fsCountOrig + 1).
|
||||
if (err == 0) {
|
||||
if (fsCount <= fsCountOrig) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
} while ( (err == 0) && ! done );
|
||||
|
||||
int i;
|
||||
int mountCounts = 0;
|
||||
for (i = 0; i < fsCount; i++) {
|
||||
if ((fsBuf[i].f_flags & MNT_LOCAL) == 0 || (fsBuf[i].f_flags & MNT_JOURNALED) == 0) {
|
||||
if (mountCounts == 0) {
|
||||
printf("UNWATCHEABLE\n");
|
||||
}
|
||||
printf("%s\n", fsBuf[i].f_mntonname);
|
||||
mountCounts++;
|
||||
}
|
||||
}
|
||||
|
||||
if (mountCounts > 0) {
|
||||
printf("#\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
free(fsBuf);
|
||||
fsBuf = NULL;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void callback(ConstFSEventStreamRef streamRef,
|
||||
void *clientCallBackInfo,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[]) {
|
||||
char **paths = eventPaths;
|
||||
|
||||
int i;
|
||||
for (i=0; i<numEvents; i++) {
|
||||
FSEventStreamEventFlags flags = eventFlags[i];
|
||||
if (flags == kFSEventStreamEventFlagMount || flags == kFSEventStreamEventFlagUnmount) {
|
||||
ReportMountedFileSystems();
|
||||
}
|
||||
else if ((flags & kFSEventStreamEventFlagMustScanSubDirs) != 0) {
|
||||
printf("RECDIRTY\n");
|
||||
printf("%s\n", paths[i]);
|
||||
}
|
||||
else if (eventFlags[i] != kFSEventStreamEventFlagNone) {
|
||||
printf("RESET\n");
|
||||
}
|
||||
else {
|
||||
printf("DIRTY\n");
|
||||
printf("%s\n", paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Static buffer for fscanf. All of the are being performed from a single thread, so it's thread safe.
|
||||
static char command[2048];
|
||||
|
||||
static void parseRoots() {
|
||||
while (TRUE) {
|
||||
fscanf(stdin, "%s", command);
|
||||
if (strcmp(command, "#") == 0 || feof(stdin)) break;
|
||||
}
|
||||
}
|
||||
|
||||
void *event_processing_thread(void *data) {
|
||||
CFStringRef mypath = CFSTR("/");
|
||||
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL);
|
||||
void *callbackInfo = NULL;
|
||||
|
||||
FSEventStreamRef stream;
|
||||
CFAbsoluteTime latency = 0.3; /* Latency in seconds */
|
||||
|
||||
// Create the stream, passing in a callback,
|
||||
stream = FSEventStreamCreate(NULL,
|
||||
&callback,
|
||||
callbackInfo,
|
||||
pathsToWatch,
|
||||
kFSEventStreamEventIdSinceNow,
|
||||
latency,
|
||||
kFSEventStreamCreateFlagNoDefer
|
||||
);
|
||||
|
||||
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
FSEventStreamStart(stream);
|
||||
|
||||
CFRunLoopRun();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main (int argc, const char * argv[]) {
|
||||
// Checking if necessary API is available (need MacOS X 10.5 or later).
|
||||
if (FSEventStreamCreate == NULL) {
|
||||
printf("GIVEUP\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ReportMountedFileSystems();
|
||||
|
||||
pthread_t thread_id;
|
||||
int rc = pthread_create(&thread_id, NULL, event_processing_thread, NULL);
|
||||
|
||||
if (rc != 0) {
|
||||
// Give up if cannot create a thread.
|
||||
printf("GIVEUP\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (TRUE) {
|
||||
fscanf(stdin, "%s", command);
|
||||
if (strcmp(command, "EXIT") == 0 || feof(stdin)) exit(0);
|
||||
if (strcmp(command, "ROOTS") == 0) parseRoots();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
gcc -framework CoreServices -o fsnotifier fsnotifier.c
|
||||
Reference in New Issue
Block a user