# HG changeset patch # User Vladimir Kvashin # Date 1479537854 -10800 # Branch release82 # Node ID 47c6fd9d2b8d02da23fd5156fe610b0645d64783 # Parent 13ee6f16f094717f3fa9f450d6d9ca01a46e8c86 Fixed #268926 (Remote File System should support DeleteOnExit) diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/DeleteOnExitSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/DeleteOnExitSupport.java Sat Nov 19 09:44:14 2016 +0300 @@ -0,0 +1,197 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + */ +package org.netbeans.modules.remote.impl.fs; + +import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import org.netbeans.modules.nativeexecution.api.NativeProcessBuilder; +import org.netbeans.modules.nativeexecution.api.util.ConnectionManager; +import org.netbeans.modules.nativeexecution.api.util.ProcessUtils; +import org.openide.util.Exceptions; + +/** + * Manages paths that are marked as "delete on exit". + * @author vkvashin + */ +public class DeleteOnExitSupport { + + private final ExecutionEnvironment execEnv; + private final File cache; + + /** If the ALLOW_ALTERNATIVE_DELETE_ON_EXIT is ON and transport does not support delete-on-exit, + * then alternative delete-on-exit will work */ + private static final boolean ALLOW_ALTERNATIVE_DELETE_ON_EXIT = + RemoteFileSystemUtils.getBoolean("remote.alternative.delete.on.exit", true); + + private static final String DELETE_ON_EXIT_FILE_NAME = ".rfs_delete_on_exit"; // NOI18N + + private static final Object lock = new Object(); + + // The idea about filesToDelete and filesToRemember is as follows: + // When a file is marked as "delete on exit", it is added to filesToRemember + // When a disconnect occurs, we move all files from filesToRemember into filesToDelete + // (and also store them on disk) + // When connnect occurs, filesToDelete are deleted. + // This prevents sync issues + + /** guarded by lock */ + private final LinkedHashSet filesToDelete = new LinkedHashSet<>(); + + /** guarded by lock */ + private final LinkedHashSet filesToRemember = new LinkedHashSet<>(); + + + public DeleteOnExitSupport(ExecutionEnvironment execEnv, File cacheRoot) { + this.execEnv = execEnv; + this.cache = new File(cacheRoot, DELETE_ON_EXIT_FILE_NAME); + if (ALLOW_ALTERNATIVE_DELETE_ON_EXIT) { + synchronized (lock) { + loadDeleteOnExit(cache, filesToDelete); + } + } + } + + /** Called directly from ConnectionListener.connected */ + public void notifyConnected() { + } + + /** Called directly from ConnectionListener.disconnected */ + public void notifyDisconnected() { + if (ALLOW_ALTERNATIVE_DELETE_ON_EXIT) { + List paths; + synchronized (lock) { + filesToDelete.addAll(filesToRemember); + filesToRemember.clear(); + paths = new ArrayList<>(filesToDelete); + } + storeDeleteOnExit(cache, paths); + } + } + /** + * Is called from the request processor + * in reaction on connect OR disconnect + */ + public void processConnectionChange() { + if (ALLOW_ALTERNATIVE_DELETE_ON_EXIT) { + if (ConnectionManager.getInstance().isConnectedTo(execEnv)) { + List paths; + synchronized (lock) { + paths = new ArrayList<>(filesToDelete); + filesToDelete.clear(); + } + if (!paths.isEmpty()) { + deleteImpl(execEnv, paths); + } + } + } + } + + public void deleteOnExit(String... paths) { + if (ALLOW_ALTERNATIVE_DELETE_ON_EXIT) { + synchronized (lock) { + for (String p : paths) { + filesToRemember.add(p); + } + } + } + } + + private static void deleteImpl(ExecutionEnvironment execEnv, Collection paths) { + assert ALLOW_ALTERNATIVE_DELETE_ON_EXIT; + if (paths.isEmpty()) { + return; + } + StringBuilder sb = new StringBuilder(); + for (String p : paths) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(p); + } + if (!ConnectionManager.getInstance().isConnectedTo(execEnv)) { + return; + } + ProcessUtils.execute(NativeProcessBuilder.newProcessBuilder(execEnv).setExecutable("xargs").setArguments("rm"), sb.toString().getBytes()); + + } + + private static void storeDeleteOnExit(File file, Collection paths) { + assert ALLOW_ALTERNATIVE_DELETE_ON_EXIT; + // the existence of cache root ensured in ctor + try (PrintWriter pw = new PrintWriter(file, "UTF8")) { // NOI18N + if (!paths.isEmpty()) { + for (String path : paths) { + pw.append(path).append('\n'); + } + pw.close(); + } + } catch (FileNotFoundException | UnsupportedEncodingException ex) { + Exceptions.printStackTrace(ex); // should never occur + } + } + + private static void loadDeleteOnExit(File file, Collection pathsToAdd) { + assert ALLOW_ALTERNATIVE_DELETE_ON_EXIT; + // the existence of cache root ensured in ctor + // this is called from ctor only, so it's OK to do file ops in sync block + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + for (String path; (path = br.readLine()) != null;) { + if (!path.isEmpty()) { + pathsToAdd.add(path); + } + } + // line is not visible here. + } catch (FileNotFoundException ex) { + // nothing to do: no file is quite normal + } catch (IOException ex) { + ex.printStackTrace(System.err); + } + } +} diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileSystem.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileSystem.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileSystem.java Sat Nov 19 09:44:14 2016 +0300 @@ -55,7 +55,6 @@ import java.io.InterruptedIOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.lang.ref.WeakReference; import java.net.ConnectException; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; @@ -140,7 +139,7 @@ private final List problemListeners = new ArrayList<>(globalProblemListeners); transient private final StatusImpl status = new StatusImpl(); - private final LinkedHashSet deleteOnExitFiles = new LinkedHashSet<>(); + private final DeleteOnExitSupport deleteOnExitSupport; private final ThreadLocal beingRemoved = new ThreadLocal<>(); private final ThreadLocal beingCreated = new ThreadLocal<>(); private final ThreadLocal externallyRemoved = new ThreadLocal<>(); @@ -169,6 +168,7 @@ * @guarded by autoMounts */ private boolean autoMountsAnalyzed = false; + private volatile boolean disposed = false; /*package*/ RemoteFileSystem(ExecutionEnvironment execEnv) throws IOException { RemoteLogger.assertTrue(execEnv.isRemote()); @@ -186,6 +186,7 @@ if (!cache.exists() && !cache.mkdirs()) { throw new IOException(NbBundle.getMessage(getClass(), "ERR_CreateDir", cache.getAbsolutePath())); // new IOException sic! (ctor) } + deleteOnExitSupport = new DeleteOnExitSupport(execEnv, cache); this.rootDelegate = new RootFileObject(this.root = new RemoteFileObject(this), this, execEnv, cache); // NOI18N factory.register(rootDelegate); @@ -326,6 +327,7 @@ } /*package*/ void dispose() { + disposed = true; ConnectionManager.getInstance().removeConnectionListener(this); } @@ -339,7 +341,7 @@ if (ConnectionManager.getInstance().isConnectedTo(execEnv)) { maintainAutoMounts(); } - if (connectionChanged) { + if (connectionChanged && !disposed) { if (ConnectionManager.getInstance().isConnectedTo(execEnv)) { refreshManager.scheduleRefreshOnConnect(); } @@ -347,6 +349,9 @@ fo.connectionChanged(); } } + if (!disposed) { + deleteOnExitSupport.processConnectionChange(); + } } private void maintainAutoMounts() { @@ -405,6 +410,7 @@ if (execEnv.equals(env)) { readOnlyConnectNotification.compareAndSet(true, false); connectionChanged = true; // volatile + deleteOnExitSupport.notifyConnected(); connectionTask.schedule(0); } } @@ -420,6 +426,7 @@ if (COLLECT_STATSISTICS) { lockSupport.printStatistics(this); } + deleteOnExitSupport.notifyDisconnected(); } public ExecutionEnvironment getExecutionEnvironment() { @@ -949,36 +956,19 @@ return status; } - public void deleteOnExit(String path) { - synchronized(deleteOnExitFiles) { - if (deleteOnExitFiles.isEmpty()) { - Runtime.getRuntime().addShutdownHook(new Thread() { + public void deleteOnExit(String... paths) { + if (RemoteFileSystemTransport.canDeleteOnDisconnect(execEnv)) { + try { + RemoteFileSystemTransport.deleteOnDisconnect(execEnv, paths); + return; + } // else + catch (IOException | java.util.concurrent.CancellationException | InterruptedException | ExecutionException ex) { + ex.printStackTrace(System.err); + } + } + deleteOnExitSupport.deleteOnExit(paths); + } - @Override - public void run() { - releaseResources(); - } - - }); - } - deleteOnExitFiles.add(path); - } - } - - private void releaseResources() { - ArrayList toBeDeleted; - synchronized(deleteOnExitFiles) { - toBeDeleted = new ArrayList<>(deleteOnExitFiles); - } - Collections.reverse(toBeDeleted); - for (String filename : toBeDeleted) { - if (!ConnectionManager.getInstance().isConnectedTo(execEnv)) { - return; - } - CommonTasksSupport.rmFile(execEnv, filename, null); - } - } - /*package*/ void setBeingRemoved(RemoteFileObjectBase fo) { beingRemoved.set(fo); } diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileSystemTransport.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileSystemTransport.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileSystemTransport.java Sat Nov 19 09:44:14 2016 +0300 @@ -83,6 +83,15 @@ public static boolean canSetAccessCheckType(ExecutionEnvironment execEnv) { return getInstanceFast(execEnv).canSetAccessCheckType(); } + + public static boolean canDeleteOnDisconnect(ExecutionEnvironment execEnv) { + return getInstanceFast(execEnv).canDeleteOnDisconnect(); + } + + public static void deleteOnDisconnect(ExecutionEnvironment execEnv, String... paths) + throws IOException, CancellationException, InterruptedException, ExecutionException { + getInstanceFast(execEnv).deleteOnDisconnect(paths); + } public static void setAccessCheckType(ExecutionEnvironment execEnv, FileSystemProvider.AccessCheckType accessCheckType) { getInstanceFast(execEnv).setAccessCheckType(accessCheckType); @@ -344,6 +353,11 @@ protected abstract boolean canSetAccessCheckType(); + protected abstract boolean canDeleteOnDisconnect(); + + protected abstract void deleteOnDisconnect(String[] paths) + throws IOException, CancellationException, InterruptedException, ExecutionException; + protected abstract void setAccessCheckType(FileSystemProvider.AccessCheckType accessCheckType); /** can be null */ diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/SftpTransport.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/SftpTransport.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/SftpTransport.java Sat Nov 19 09:44:14 2016 +0300 @@ -296,4 +296,13 @@ protected FileSystemProvider.AccessCheckType getAccessCheckType() { return null; } + + @Override + protected boolean canDeleteOnDisconnect() { + return false; + } + + @Override + protected void deleteOnDisconnect(String[] paths) { + } } diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSDispatcher.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSDispatcher.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSDispatcher.java Sat Nov 19 09:44:14 2016 +0300 @@ -146,7 +146,7 @@ Exceptions.printStackTrace(ex); } } - return "1.10.2"; // NOI18N + return "1.11.0"; // NOI18N } private FSSDispatcher(ExecutionEnvironment env) { diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSRequestKind.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSRequestKind.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSRequestKind.java Sat Nov 19 09:44:14 2016 +0300 @@ -56,6 +56,7 @@ FS_REQ_REMOVE_WATCH('w'), FS_REQ_REFRESH('R'), FS_REQ_DELETE('d'), + FS_REQ_DELETE_ON_DISCONNECT('D'), FS_REQ_COPY('C'), FS_REQ_MOVE('m'), FS_REQ_SERVER_INFO('i'), diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSTransport.java --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSTransport.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/server/FSSTransport.java Sat Nov 19 09:44:14 2016 +0300 @@ -657,6 +657,20 @@ protected FileSystemProvider.AccessCheckType getAccessCheckType() { return dispatcher.getAccessCheckType(); } + + @Override + protected boolean canDeleteOnDisconnect() { + return true; + } + + @Override + protected void deleteOnDisconnect(String[] paths) + throws IOException, CancellationException, InterruptedException, ExecutionException { + for (String p : paths) { + FSSRequest request = new FSSRequest(FSSRequestKind.FS_REQ_DELETE_ON_DISCONNECT, p, true); + dispatcher.dispatch(request); + } + } private class WarmupImpl implements Warmup, FSSResponse.Listener, Runnable { diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/test/unit/src/org/netbeans/modules/remote/impl/fs/DeleteOnExitTestCase.java --- a/dlight.remote.impl/test/unit/src/org/netbeans/modules/remote/impl/fs/DeleteOnExitTestCase.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/test/unit/src/org/netbeans/modules/remote/impl/fs/DeleteOnExitTestCase.java Sat Nov 19 09:44:14 2016 +0300 @@ -80,10 +80,11 @@ dirFO.getFileSystem().deleteOnExit(path1); dirFO.getFileSystem().deleteOnExit(path2); - reconnect(); - + //RemoteFileSystemManager.getInstance().resetFileSystem(execEnv, false); + reconnect(true); + sleep(200); status = ProcessUtils.execute(execEnv, "ls", path1, path2); - assertFalse("Files should be removed", status.isOK()); + assertExec("Files should be removed", false, 100, 5, "ls", path1, path2); } finally { removeRemoteDirIfNotNull(dir); } @@ -97,15 +98,24 @@ RemoteFileObject dirFO = (RemoteFileObject) getFileObject(dir); FileObject tmpFO = dirFO.getFileSystem().createTempFile(dirFO, "tmp", "tmp", true); String path1 = tmpFO.getPath(); - //ProcessUtils.ExitStatus status = ProcessUtils.execute(execEnv, "ls", path1); - //assertTrue("Error creating temp files", status.isOK()); - reconnect(); - final ProcessUtils.ExitStatus status = ProcessUtils.execute(execEnv, "ls", path1); - assertFalse("Files should be removed", status.isOK()); + //RemoteFileSystemManager.getInstance().resetFileSystem(execEnv, false); + reconnect(true); + assertExec("Files should be removed", false, 100, 5, "ls", path1); } finally { removeRemoteDirIfNotNull(dir); } } + + private void assertExec(String failureMessage, boolean expectSuccess, int timeout, int attempts, String cmd, String...args) { + for (int i = 0; i < attempts; i++) { + ProcessUtils.ExitStatus status = ProcessUtils.execute(execEnv, cmd, args); + if (status.isOK() == expectSuccess) { + return; + } + sleep(timeout); + } + assertTrue(failureMessage, false); + } public static Test suite() { return RemoteApiTest.createSuite(DeleteOnExitTestCase.class); diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/test/unit/src/org/netbeans/modules/remote/impl/fs/RemoteFileTestBase.java --- a/dlight.remote.impl/test/unit/src/org/netbeans/modules/remote/impl/fs/RemoteFileTestBase.java Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/test/unit/src/org/netbeans/modules/remote/impl/fs/RemoteFileTestBase.java Sat Nov 19 09:44:14 2016 +0300 @@ -106,12 +106,13 @@ this.execEnv = execEnv; } - protected void reconnect() throws Exception { + protected void reconnect(boolean resetFileSystem) throws Exception { char[] paswd = PasswordManager.getInstance().getPassword(execEnv); ConnectionManager.getInstance().disconnect(execEnv); - sleep(250); + sleep(100); assertFalse("Failure disconnecting from " + execEnv, ConnectionManager.getInstance().isConnectedTo(execEnv)); - sleep(250); + sleep(100); + RemoteFileSystemManager.getInstance().resetFileSystem(execEnv, false); PasswordManager.getInstance().storePassword(execEnv, paswd, true); ConnectionManager.getInstance().connectTo(execEnv); assertTrue("Failure reconnecting to " + execEnv, ConnectionManager.getInstance().isConnectedTo(execEnv)); diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/tools/fs_server/src/fs_server.c --- a/dlight.remote.impl/tools/fs_server/src/fs_server.c Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/tools/fs_server/src/fs_server.c Sat Nov 19 09:44:14 2016 +0300 @@ -96,8 +96,8 @@ //static bool shutting_down = false; #define FS_SERVER_MAJOR_VERSION 1 -#define FS_SERVER_MID_VERSION 10 -#define FS_SERVER_MINOR_VERSION 3 +#define FS_SERVER_MID_VERSION 11 +#define FS_SERVER_MINOR_VERSION 0 typedef struct fs_entry { int /*short?*/ name_len; @@ -130,9 +130,45 @@ char* strerr; } err_info; +static struct { + queue queue; + pthread_mutex_t mutex; +} delete_on_exit_list; + static const int thread_emsg_bufsize = PATH_MAX * 2 + 128; // should it be less? static const int strerr_bufsize = PATH_MAX * 2 + 128; // should it be less? +static void delete_on_exit_list_init() { + pthread_mutex_init(&delete_on_exit_list.mutex, NULL); + mutex_lock_wrapper(&delete_on_exit_list.mutex); + queue_init(&delete_on_exit_list.queue); + mutex_unlock_wrapper(&delete_on_exit_list.mutex); +} + +static void delete_on_exit_list_add(const char* p) { + mutex_lock_wrapper(&delete_on_exit_list.mutex); + const char* p2 = strdup_wrapper(p); + queue_add(&delete_on_exit_list.queue, (void*) p2); + mutex_unlock_wrapper(&delete_on_exit_list.mutex); +} + +static void delete_on_exit_impl() { + trace(TRACE_INFO, "Processing files that should be deleted on exit\n"); + mutex_lock_wrapper(&delete_on_exit_list.mutex); + int cnt = 0; + void* p; + while((p = queue_poll(&delete_on_exit_list.queue))) { + const char* path = (const char*) p; + trace(TRACE_FINEST, " removing %s...", path); + unlink(path); + free(p); + cnt++; + free(p); + } + mutex_unlock_wrapper(&delete_on_exit_list.mutex); + trace(TRACE_INFO, "Removed %d files\n", cnt); +} + static void err_init() { err_info.err_no = 0; err_info.errmsg = malloc_wrapper(thread_emsg_bufsize); @@ -303,6 +339,7 @@ case FS_REQ_REMOVE_WATCH: case FS_REQ_REFRESH: case FS_REQ_DELETE: + case FS_REQ_DELETE_ON_DISCONNECT: case FS_REQ_SERVER_INFO: case FS_REQ_HELP: case FS_REQ_OPTION: @@ -849,6 +886,13 @@ my_fflush(STDOUT); } +static void response_delete_on_disconnect(int request_id, const char* path) { + if (request_id != 0) { + response_error(request_id, path, 0, "FS_REQ_DELETE_ON_DISCONNECT request should have zero ID!"); + } + delete_on_exit_list_add(path); +} + static void response_delete(int request_id, const char* path, const settings_str* settings) { const char* last_slash = strrchr(path, '/'); @@ -1354,6 +1398,7 @@ static void thread_init() { err_init(); + delete_on_exit_list_init(); sigset_t set; sigfillset(&set); sigdelset(&set, SIGUSR1); @@ -1524,6 +1569,7 @@ help_req_kind(FS_REQ_REMOVE_WATCH); help_req_kind(FS_REQ_REFRESH); help_req_kind(FS_REQ_DELETE); + help_req_kind(FS_REQ_DELETE_ON_DISCONNECT); help_req_kind(FS_REQ_SERVER_INFO); help_req_kind(FS_REQ_OPTION); help_req_kind(FS_REQ_HELP); @@ -1537,6 +1583,9 @@ case FS_REQ_DELETE: response_delete(request->id, request->path, &settings); break; + case FS_REQ_DELETE_ON_DISCONNECT: + response_delete_on_disconnect(request->id, request->path); + break; case FS_REQ_SERVER_INFO: response_info(request->id); break; @@ -1957,6 +2006,7 @@ static void shutdown() { state_set_proceed(false); + delete_on_exit_impl(); blocking_queue_shutdown(&req_queue); trace(TRACE_INFO, "Max. requests queue size: %d\n", blocking_queue_max_size(&req_queue)); if (statistics) { diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/tools/fs_server/src/fs_server.h --- a/dlight.remote.impl/tools/fs_server/src/fs_server.h Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/tools/fs_server/src/fs_server.h Sat Nov 19 09:44:14 2016 +0300 @@ -62,6 +62,7 @@ FS_REQ_REMOVE_WATCH = 'w', FS_REQ_REFRESH = 'R', FS_REQ_DELETE = 'd', + FS_REQ_DELETE_ON_DISCONNECT = 'D', FS_REQ_SERVER_INFO = 'i', FS_REQ_HELP = '?', FS_REQ_OPTION = 'o' diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/tools/fs_server/src/util.c --- a/dlight.remote.impl/tools/fs_server/src/util.c Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/tools/fs_server/src/util.c Sat Nov 19 09:44:14 2016 +0300 @@ -146,6 +146,14 @@ va_end(args); } } +char *strdup_wrapper(const char* str) { + char* p = strdup(str); + if (!p) { + report_error("out of memory\n"); + exit(FAILURE_ALLOCATE_MEMORY); + } + return p; +} void *malloc_wrapper(size_t size) { void *p = malloc(size); diff -r 13ee6f16f094 -r 47c6fd9d2b8d dlight.remote.impl/tools/fs_server/src/util.h --- a/dlight.remote.impl/tools/fs_server/src/util.h Wed Nov 16 13:27:01 2016 +0300 +++ b/dlight.remote.impl/tools/fs_server/src/util.h Sat Nov 19 09:44:14 2016 +0300 @@ -89,6 +89,7 @@ void mutex_lock_wrapper(pthread_mutex_t *mutex); void *malloc_wrapper(size_t size); void *realloc_wrapper(void *ptr, size_t size); +char *strdup_wrapper(const char* str); bool get_home_dir(char* home, int size); bool file_exists(const char* path);