Skip to content

Commit

Permalink
initial Linux platform and scripts
Browse files Browse the repository at this point in the history
This is an initial port of the GVFS.Platform.Linux classes and
the Scripts/Linux scripts from both the Mac equivalents in this
repository as well as the corresponding classes in the VFSForGit
repository.

Quoting from the primary VFSForGit commit
microsoft/VFSForGit@b304cf7
from which this work was derived:

    We add the core GVFS.Platform.Linux classes, derived from their
    GVFS.Platform.Mac equivalents but with appropriate changes for
    syscall argument signatures and flag values, type definitions,
    and structure fields (e.g., mode_t is a uint, and the layout of
    struct stat is quite different).

We also include all or portions of
microsoft/VFSForGit@4ec09a8,
microsoft/VFSForGit@7c3c4fa, and
microsoft/VFSForGit@ef3201b.

Co-authored-by: Ashe Connor <ashe@kivikakk.ee>
  • Loading branch information
chrisd8088 and kivikakk committed Aug 18, 2020
1 parent 2dc2a2e commit 5e942ea
Show file tree
Hide file tree
Showing 11 changed files with 596 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<BaseIntermediateOutputPath>$(ProjectOutPath)obj\</BaseIntermediateOutputPath>

<!-- Common build properties -->
<RuntimeIdentifiers>win10-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<Deterministic>true</Deterministic>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<CodeAnalysisRuleSet>$(RepoPath)Scalar.ruleset</CodeAnalysisRuleSet>
Expand Down
133 changes: 133 additions & 0 deletions Scalar.Common/Platforms/Linux/LinuxFileBasedLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using Scalar.Common;
using Scalar.Common.FileSystem;
using Scalar.Common.Tracing;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Scalar.Platform.Linux
{
public class LinuxFileBasedLock : FileBasedLock
{
private int lockFileDescriptor;

public LinuxFileBasedLock(
PhysicalFileSystem fileSystem,
ITracer tracer,
string lockPath)
: base(fileSystem, tracer, lockPath)
{
this.lockFileDescriptor = NativeMethods.InvalidFileDescriptor;
}

public override bool TryAcquireLock()
{
if (this.lockFileDescriptor == NativeMethods.InvalidFileDescriptor)
{
this.FileSystem.CreateDirectory(Path.GetDirectoryName(this.LockPath));

this.lockFileDescriptor = NativeMethods.Open(
this.LockPath,
NativeMethods.OpenCreate | NativeMethods.OpenWriteOnly,
NativeMethods.FileMode644);

if (this.lockFileDescriptor == NativeMethods.InvalidFileDescriptor)
{
int errno = Marshal.GetLastWin32Error();
EventMetadata metadata = this.CreateEventMetadata(errno);
this.Tracer.RelatedWarning(
metadata,
$"{nameof(LinuxFileBasedLock)}.{nameof(this.TryAcquireLock)}: Failed to open lock file");

return false;
}
}

if (NativeMethods.FLock(this.lockFileDescriptor, NativeMethods.LockEx | NativeMethods.LockNb) != 0)
{
int errno = Marshal.GetLastWin32Error();
if (errno != NativeMethods.EIntr && errno != NativeMethods.EWouldBlock)
{
EventMetadata metadata = this.CreateEventMetadata(errno);
this.Tracer.RelatedWarning(
metadata,
$"{nameof(LinuxFileBasedLock)}.{nameof(this.TryAcquireLock)}: Unexpected error when locking file");
}

return false;
}

return true;
}

public override void Dispose()
{
if (this.lockFileDescriptor != NativeMethods.InvalidFileDescriptor)
{
if (NativeMethods.Close(this.lockFileDescriptor) != 0)
{
// Failures of close() are logged for diagnostic purposes only.
// It's possible that errors from a previous operation (e.g. write(2))
// are only reported in close(). We should *not* retry the close() if
// it fails since it may cause a re-used file descriptor from another
// thread to be closed.

int errno = Marshal.GetLastWin32Error();
EventMetadata metadata = this.CreateEventMetadata(errno);
this.Tracer.RelatedWarning(
metadata,
$"{nameof(LinuxFileBasedLock)}.{nameof(this.Dispose)}: Error when closing lock fd");
}

this.lockFileDescriptor = NativeMethods.InvalidFileDescriptor;
}
}

private EventMetadata CreateEventMetadata(int errno = 0)
{
EventMetadata metadata = new EventMetadata();
metadata.Add("Area", nameof(LinuxFileBasedLock));
metadata.Add(nameof(this.LockPath), this.LockPath);
if (errno != 0)
{
metadata.Add(nameof(errno), errno);
}

return metadata;
}

private static class NativeMethods
{
// #define O_WRONLY 0x0001 /* open for writing only */
public const int OpenWriteOnly = 0x0001;

// #define O_CREAT 0x0200 /* create if nonexistant */
public const int OpenCreate = 0x0040;

// #define EINTR 4 /* Interrupted system call */
public const int EIntr = 4;

// #define EAGAIN 11 /* Resource temporarily unavailable */
// #define EWOULDBLOCK EAGAIN /* Operation would block */
public const int EWouldBlock = 11;

public const int LockSh = 1; // #define LOCK_SH 1 /* shared lock */
public const int LockEx = 2; // #define LOCK_EX 2 /* exclusive lock */
public const int LockNb = 4; // #define LOCK_NB 4 /* don't block when locking */
public const int LockUn = 8; // #define LOCK_UN 8 /* unlock */

public const int InvalidFileDescriptor = -1;

public static readonly uint FileMode644 = Convert.ToUInt32("644", 8);

[DllImport("libc", EntryPoint = "open", SetLastError = true)]
public static extern int Open(string pathname, int flags, uint mode);

[DllImport("libc", EntryPoint = "close", SetLastError = true)]
public static extern int Close(int fd);

[DllImport("libc", EntryPoint = "flock", SetLastError = true)]
public static extern int FLock(int fd, int operation);
}
}
}
110 changes: 110 additions & 0 deletions Scalar.Common/Platforms/Linux/LinuxFileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using Scalar.Common;
using Scalar.Platform.POSIX;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Scalar.Platform.Linux
{
public class LinuxFileSystem : POSIXFileSystem
{
public override bool IsExecutable(string fileName)
{
NativeStat.StatBuffer statBuffer = this.StatFile(fileName);
return NativeStat.IsExecutable(statBuffer.Mode);
}

public override bool IsSocket(string fileName)
{
NativeStat.StatBuffer statBuffer = this.StatFile(fileName);
return NativeStat.IsSock(statBuffer.Mode);
}

public override bool IsFileSystemSupported(string path, out string error)
{
error = null;
return true;
}

private NativeStat.StatBuffer StatFile(string fileName)
{
if (NativeStat.Stat(fileName, out NativeStat.StatBuffer statBuffer) != 0)
{
NativeMethods.ThrowLastWin32Exception($"Failed to stat {fileName}");
}

return statBuffer;
}

private static class NativeStat
{
// #define S_IFMT 0170000 /* [XSI] type of file mask */
private static readonly uint IFMT = Convert.ToUInt32("170000", 8);

// #define S_IFSOCK 0140000 /* [XSI] socket */
private static readonly uint IFSOCK = Convert.ToUInt32("0140000", 8);

// #define S_IXUSR 0000100 /* [XSI] X for owner */
private static readonly uint IXUSR = Convert.ToUInt32("100", 8);

// #define S_IXGRP 0000010 /* [XSI] X for group */
private static readonly uint IXGRP = Convert.ToUInt32("10", 8);

// #define S_IXOTH 0000001 /* [XSI] X for other */
private static readonly uint IXOTH = Convert.ToUInt32("1", 8);

// #define _STAT_VER 1
private static readonly int STAT_VER = 1;

public static bool IsSock(uint mode)
{
// #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
return (mode & IFMT) == IFSOCK;
}

public static bool IsExecutable(uint mode)
{
return (mode & (IXUSR | IXGRP | IXOTH)) != 0;
}

public static int Stat(string path, [Out] out StatBuffer buf)
{
return __XStat64(STAT_VER, path, out buf);
}

// TODO(Linux): assumes recent GNU libc or ABI-compatible libc
[DllImport("libc", EntryPoint = "__xstat64", SetLastError = true)]
private static extern int __XStat64(int vers, string path, [Out] out StatBuffer buf);

[StructLayout(LayoutKind.Sequential)]
public struct TimeSpec
{
public long Sec;
public long Nsec;
}

// TODO(Linux): assumes stat64 field layout of x86-64 architecture
[StructLayout(LayoutKind.Sequential)]
public struct StatBuffer
{
public ulong Dev; /* ID of device containing file */
public ulong Ino; /* File serial number */
public ulong NLink; /* Number of hard links */
public uint Mode; /* Mode of file (see below) */
public uint UID; /* User ID of the file */
public uint GID; /* Group ID of the file */
public uint Padding; /* RESERVED: DO NOT USE! */
public ulong RDev; /* Device ID if special file */
public long Size; /* file size, in bytes */
public long BlkSize; /* optimal blocksize for I/O */
public long Blocks; /* blocks allocated for file */
public TimeSpec ATimespec; /* time of last access */
public TimeSpec MTimespec; /* time of last data modification */
public TimeSpec CTimespec; /* time of last status change */

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
private long[] reserved; /* RESERVED: DO NOT USE! */
}
}
}
}
37 changes: 37 additions & 0 deletions Scalar.Common/Platforms/Linux/LinuxPlatform.Shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.IO;
using Scalar.Common;
using Scalar.Platform.POSIX;

namespace Scalar.Platform.Linux
{
public partial class LinuxPlatform
{
public static string GetDataRootForScalarImplementation()
{
// TODO(Linux): determine installation location and data path
string path = Environment.GetEnvironmentVariable("SCALAR_DATA_PATH");
return path ?? "/var/run/scalar";
}

public static string GetDataRootForScalarComponentImplementation(string componentName)
{
return Path.Combine(GetDataRootForScalarImplementation(), componentName);
}

public static string GetUpgradeHighestAvailableVersionDirectoryImplementation()
{
return GetUpgradeNonProtectedDirectoryImplementation();
}

public static string GetUpgradeNonProtectedDirectoryImplementation()
{
return Path.Combine(GetDataRootForScalarImplementation(), ProductUpgraderInfo.UpgradeDirectoryName);
}

private string GetUpgradeNonProtectedDataDirectory()
{
return GetUpgradeNonProtectedDirectoryImplementation();
}
}
}
Loading

0 comments on commit 5e942ea

Please sign in to comment.