diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs
index efd09a7a7..58175a9a3 100644
--- a/ArchiSteamFarm/ASF.cs
+++ b/ArchiSteamFarm/ASF.cs
@@ -305,7 +305,7 @@ namespace ArchiSteamFarm {
string executable = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.AssemblyName);
if (File.Exists(executable)) {
- OS.UnixSetFileAccessExecutable(executable);
+ OS.UnixSetFileAccess(executable, OS.EUnixPermission.Combined755);
}
}
diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj
index 1c4b90e69..5a836e0fb 100644
--- a/ArchiSteamFarm/ArchiSteamFarm.csproj
+++ b/ArchiSteamFarm/ArchiSteamFarm.csproj
@@ -69,6 +69,7 @@
+
diff --git a/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs b/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs
new file mode 100644
index 000000000..d9d04c222
--- /dev/null
+++ b/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs
@@ -0,0 +1,173 @@
+// _ _ _ ____ _ _____
+// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
+// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
+// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
+// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
+// |
+// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
+// Contact: JustArchi@JustArchi.net
+// |
+// 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.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Security.AccessControl;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+
+namespace ArchiSteamFarm.Helpers {
+ internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore {
+ private const int SpinLockDelay = 1000; // In milliseconds
+
+ private readonly string FilePath;
+ private readonly SemaphoreSlim LocalSemaphore = new SemaphoreSlim(1, 1);
+
+ private FileStream FileLock;
+
+ internal CrossProcessFileBasedSemaphore([NotNull] string name) {
+ if (string.IsNullOrEmpty(name)) {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ FilePath = Path.Combine(Path.GetTempPath(), SharedInfo.ASF, name);
+
+ EnsureFileExists();
+ }
+
+ public void Dispose() {
+ LocalSemaphore.Dispose();
+
+ FileLock?.Dispose();
+ }
+
+ void ICrossProcessSemaphore.Release() {
+ lock (LocalSemaphore) {
+ if (FileLock == null) {
+ throw new ArgumentNullException(nameof(FileLock));
+ }
+
+ FileLock.Dispose();
+ FileLock = null;
+ }
+
+ LocalSemaphore.Release();
+ }
+
+ async Task ICrossProcessSemaphore.WaitAsync() {
+ await LocalSemaphore.WaitAsync().ConfigureAwait(false);
+
+ bool success = false;
+
+ try {
+ lock (LocalSemaphore) {
+ if (FileLock != null) {
+ throw new ArgumentNullException(nameof(FileLock));
+ }
+
+ while (true) {
+ EnsureFileExists();
+
+ try {
+ FileLock = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None);
+ success = true;
+
+ return;
+ } catch (IOException) {
+ Thread.Sleep(SpinLockDelay);
+ }
+ }
+ }
+ } finally {
+ if (!success) {
+ LocalSemaphore.Release();
+ }
+ }
+ }
+
+ async Task ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) {
+ Stopwatch stopwatch = Stopwatch.StartNew();
+
+ if (!await LocalSemaphore.WaitAsync(millisecondsTimeout).ConfigureAwait(false)) {
+ stopwatch.Stop();
+
+ return false;
+ }
+
+ stopwatch.Stop();
+
+ bool success = false;
+
+ try {
+ millisecondsTimeout -= (int) stopwatch.ElapsedMilliseconds;
+
+ if (millisecondsTimeout <= 0) {
+ return false;
+ }
+
+ lock (LocalSemaphore) {
+ if (FileLock != null) {
+ throw new ArgumentNullException(nameof(FileLock));
+ }
+
+ while (true) {
+ EnsureFileExists();
+
+ try {
+ FileLock = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None);
+ success = true;
+
+ return true;
+ } catch (IOException) {
+ if (millisecondsTimeout <= SpinLockDelay) {
+ return false;
+ }
+
+ Thread.Sleep(SpinLockDelay);
+ millisecondsTimeout -= SpinLockDelay;
+ }
+ }
+ }
+ } finally {
+ if (!success) {
+ LocalSemaphore.Release();
+ }
+ }
+ }
+
+ private void EnsureFileExists() {
+ Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
+
+ FileInfo fileInfo = new FileInfo(FilePath);
+
+ if (fileInfo.Exists) {
+ return;
+ }
+
+ try {
+ using (new FileStream(FilePath, FileMode.CreateNew)) { }
+
+ if (OS.IsUnix) {
+ OS.UnixSetFileAccess(FilePath, OS.EUnixPermission.Combined777);
+ } else {
+ FileSecurity fileSecurity = new FileSecurity(FilePath, AccessControlSections.All);
+
+ fileInfo.SetAccessControl(fileSecurity);
+ }
+ } catch (IOException) {
+ // Ignored, if the file was already created in the meantime by another instance, this is fine
+ }
+ }
+ }
+}
diff --git a/ArchiSteamFarm/Helpers/CrossProcessMutexBasedSemaphore.cs b/ArchiSteamFarm/Helpers/CrossProcessMutexBasedSemaphore.cs
deleted file mode 100644
index 87a8033c8..000000000
--- a/ArchiSteamFarm/Helpers/CrossProcessMutexBasedSemaphore.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-// _ _ _ ____ _ _____
-// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
-// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
-// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
-// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
-// |
-// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
-// Contact: JustArchi@JustArchi.net
-// |
-// 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.
-
-using System;
-using System.Diagnostics;
-using System.Threading;
-using System.Threading.Tasks;
-using JetBrains.Annotations;
-
-namespace ArchiSteamFarm.Helpers {
- internal sealed class CrossProcessMutexBasedSemaphore : ICrossProcessSemaphore {
- private const int SpinLockDelay = 1000; // In milliseconds
-
- private readonly SemaphoreSlim LocalSemaphore = new SemaphoreSlim(1, 1);
-
- private readonly string Name;
-
- private Mutex Mutex;
- private TaskCompletionSource ReleasedTask = new TaskCompletionSource();
-
- internal CrossProcessMutexBasedSemaphore([NotNull] string name) {
- if (string.IsNullOrEmpty(name)) {
- throw new ArgumentNullException(nameof(name));
- }
-
- Name = "Global\\" + name;
- ReleasedTask.SetResult(true);
- }
-
- public void Dispose() {
- LocalSemaphore.Dispose();
-
- Mutex?.Dispose();
- }
-
- void ICrossProcessSemaphore.Release() {
- if (Mutex == null) {
- throw new ArgumentNullException(nameof(Mutex) + " || " + nameof(ReleasedTask));
- }
-
- lock (LocalSemaphore) {
- if (Mutex == null) {
- throw new ArgumentNullException(nameof(Mutex) + " || " + nameof(ReleasedTask));
- }
-
- Mutex.Dispose();
- Mutex = null;
-
- ReleasedTask.SetResult(true);
- }
- }
-
- async Task ICrossProcessSemaphore.WaitAsync() {
- await LocalSemaphore.WaitAsync().ConfigureAwait(false);
-
- try {
- while (true) {
- if (Mutex != null) {
- await Task.Delay(SpinLockDelay).ConfigureAwait(false);
-
- continue;
- }
-
- // ReSharper disable once InconsistentlySynchronizedField - we do not synchronize TaskCompletionSource alone, but the whole process
- await ReleasedTask.Task.ConfigureAwait(false);
-
- lock (LocalSemaphore) {
- Mutex mutex = new Mutex(false, Name);
-
- try {
- mutex.WaitOne();
- } catch (AbandonedMutexException) {
- // Ignored, this is fine, other ASF process has been closed
- }
-
- ReleasedTask = new TaskCompletionSource();
- Mutex = mutex;
-
- return;
- }
- }
- } finally {
- LocalSemaphore.Release();
- }
- }
-
- async Task ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) {
- Stopwatch stopwatch = Stopwatch.StartNew();
-
- if (!await LocalSemaphore.WaitAsync(millisecondsTimeout).ConfigureAwait(false)) {
- stopwatch.Stop();
-
- return false;
- }
-
- try {
- stopwatch.Stop();
-
- millisecondsTimeout -= (int) stopwatch.ElapsedMilliseconds;
-
- if (millisecondsTimeout <= 0) {
- return false;
- }
-
- while (true) {
- if (Mutex != null) {
- if (millisecondsTimeout <= SpinLockDelay) {
- return false;
- }
-
- await Task.Delay(SpinLockDelay).ConfigureAwait(false);
- millisecondsTimeout -= SpinLockDelay;
-
- continue;
- }
-
- // ReSharper disable InconsistentlySynchronizedField - we do not synchronize TaskCompletionSource alone, but the whole process
-
- if (!ReleasedTask.Task.IsCompleted) {
- stopwatch.Restart();
-
- if (await Task.WhenAny(ReleasedTask.Task, Task.Delay(millisecondsTimeout)).ConfigureAwait(false) != ReleasedTask.Task) {
- return false;
- }
-
- stopwatch.Stop();
-
- millisecondsTimeout -= (int) stopwatch.ElapsedMilliseconds;
-
- if (millisecondsTimeout <= 0) {
- return false;
- }
- }
-
- // ReSharper restore InconsistentlySynchronizedField
-
- lock (LocalSemaphore) {
- Mutex mutex = new Mutex(false, Name);
-
- try {
- if (!mutex.WaitOne(millisecondsTimeout)) {
- return false;
- }
- } catch (AbandonedMutexException) {
- // Ignored, this is fine, other ASF process has been closed
- }
-
- ReleasedTask = new TaskCompletionSource();
- Mutex = mutex;
-
- return true;
- }
- }
- } finally {
- LocalSemaphore.Release();
- }
- }
- }
-}
diff --git a/ArchiSteamFarm/Helpers/NonCrossProcessLocalSemaphore.cs b/ArchiSteamFarm/Helpers/NonCrossProcessLocalSemaphore.cs
deleted file mode 100644
index 0e0a07a68..000000000
--- a/ArchiSteamFarm/Helpers/NonCrossProcessLocalSemaphore.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// _ _ _ ____ _ _____
-// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
-// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
-// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
-// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
-// |
-// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
-// Contact: JustArchi@JustArchi.net
-// |
-// 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.
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using JetBrains.Annotations;
-
-namespace ArchiSteamFarm.Helpers {
- internal sealed class NonCrossProcessLocalSemaphore : ICrossProcessSemaphore {
- private readonly SemaphoreSlim LocalSemaphore;
-
- internal NonCrossProcessLocalSemaphore([NotNull] string name) {
- if (string.IsNullOrEmpty(name)) {
- throw new ArgumentNullException(nameof(name));
- }
-
- LocalSemaphore = new SemaphoreSlim(1, 1);
- }
-
- public void Dispose() => LocalSemaphore.Dispose();
-
- void ICrossProcessSemaphore.Release() => LocalSemaphore.Release();
-
- [NotNull]
- Task ICrossProcessSemaphore.WaitAsync() => LocalSemaphore.WaitAsync();
-
- [NotNull]
- Task ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) => LocalSemaphore.WaitAsync(millisecondsTimeout);
- }
-}
diff --git a/ArchiSteamFarm/OS.cs b/ArchiSteamFarm/OS.cs
index f358429fe..fb0d53308 100644
--- a/ArchiSteamFarm/OS.cs
+++ b/ArchiSteamFarm/OS.cs
@@ -76,10 +76,7 @@ namespace ArchiSteamFarm {
// CrossProcessSemaphore is currently available only for Windows platforms, we use alternative synchronization for other OSes
ASF.ArchiLogger.LogGenericDebuggingException(e);
- // CrossProcessMutexBasedSemaphore disabled until reason for segfault on Linux is found
- //return new CrossProcessMutexBasedSemaphore(resourceName);
-
- return new NonCrossProcessLocalSemaphore(resourceName);
+ return new CrossProcessFileBasedSemaphore(resourceName);
}
}
@@ -132,7 +129,7 @@ namespace ArchiSteamFarm {
return true;
}
- internal static void UnixSetFileAccessExecutable(string path) {
+ internal static void UnixSetFileAccess(string path, EUnixPermission permission) {
if (string.IsNullOrEmpty(path) || !File.Exists(path)) {
ASF.ArchiLogger.LogNullError(nameof(path));
@@ -144,7 +141,7 @@ namespace ArchiSteamFarm {
}
// Chmod() returns 0 on success, -1 on failure
- if (NativeMethods.Chmod(path, (int) NativeMethods.UnixExecutePermission) != 0) {
+ if (NativeMethods.Chmod(path, (int) permission) != 0) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Marshal.GetLastWin32Error()));
}
}
@@ -206,11 +203,25 @@ namespace ArchiSteamFarm {
}
}
+ [Flags]
+ internal enum EUnixPermission : ushort {
+ OtherExecute = 0x1,
+ OtherWrite = 0x2,
+ OtherRead = 0x4,
+ GroupExecute = 0x8,
+ GroupWrite = 0x10,
+ GroupRead = 0x20,
+ UserExecute = 0x40,
+ UserWrite = 0x80,
+ UserRead = 0x100,
+ Combined755 = UserRead | UserWrite | UserExecute | GroupRead | GroupExecute | OtherRead | OtherExecute,
+ Combined777 = UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute
+ }
+
private static class NativeMethods {
internal const EExecutionState AwakeExecutionState = EExecutionState.SystemRequired | EExecutionState.AwayModeRequired | EExecutionState.Continuous;
internal const uint EnableQuickEditMode = 0x0040;
internal const sbyte StandardInputHandle = -10;
- internal const EUnixPermission UnixExecutePermission = EUnixPermission.UserRead | EUnixPermission.UserWrite | EUnixPermission.UserExecute | EUnixPermission.GroupRead | EUnixPermission.GroupExecute | EUnixPermission.OtherRead | EUnixPermission.OtherExecute;
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
internal static extern int Chmod(string path, int mode);
@@ -234,22 +245,6 @@ namespace ArchiSteamFarm {
AwayModeRequired = 0x00000040,
Continuous = 0x80000000
}
-
- [Flags]
- internal enum EUnixPermission : ushort {
- OtherExecute = 0x1,
- OtherRead = 0x4,
- GroupExecute = 0x8,
- GroupRead = 0x20,
- UserExecute = 0x40,
- UserWrite = 0x80,
- UserRead = 0x100
-
- /*
- OtherWrite = 0x2
- GroupWrite = 0x10
- */
- }
}
}
}