🍎

UnityでmacOSのファイルパーミッションを取得する

2025/01/31に公開

はじめに

macOS上のUniyからzipファイルを圧縮する際に、実行ファイルのパーミッションを反映する必要がありました。
C#は FileSystemInfo.UnixFileModeMono.Posix などのライブラリを使うことでファイルパーミッションを取得できますがUnityでは利用できませんでした。
そこで stat(2) システムコールを使ってmacOSのファイルパーミッションを取得する方法を調べました。

コード

#if UNITY_STANDALONE_OSX
using System;
using System.Runtime.InteropServices;

[Flags]
public enum Mode
{
	SetUserId    = 0b_100_000_000_000,
	SetGroupId   = 0b_010_000_000_000,
	StickyBit    = 0b_001_000_000_000,
	ReadUser     = 0b_000_100_000_000,
	WriteUser    = 0b_000_010_000_000,
	ExecuteUser  = 0b_000_001_000_000,
	ReadGroup    = 0b_000_000_100_000,
	WriteGroup   = 0b_000_000_010_000,
	ExecuteGroup = 0b_000_000_001_000,
	ReadOther    = 0b_000_000_000_100,
	WriteOther   = 0b_000_000_000_010,
	ExecuteOther = 0b_000_000_000_001,
}

public static class SysCall
{
	[StructLayout(LayoutKind.Sequential)]
	private struct Stat
	{
		public Int32 st_dev;
		public UInt16 st_mode;
		public UInt16 st_nlink;
		public UInt64 st_no;
		public ulong st_uid;
		public uint st_gid;
		public uint st_rdev;
		public long st_atime;
		public long st_atimensec;
		public long st_mtime;
		public long st_mtimensec;
		public long st_ctime;
		public long st_ctimensec;
		public long st_birthtime;
		public long st_birthtimensec;
		public Int64 st_size;
		public Int64 st_blocks;
		public Int32 st_blocksize;
		public UInt32 st_flags;
		public UInt32 st_gen;
		public UInt32 st_lspare;
		public UInt64 st_qspare1;
		public UInt64 st_qspare2;
	}

	[DllImport("libc", EntryPoint = "stat", SetLastError = true)]
	private static extern int stat(string path, out Stat stat);

	/// <summary>
	/// ファイルのパーミッションを取得します
	/// <param name="path">ファイルのパス</param>
	/// <param name="mode">取得したパーミッション</param>
	/// </summary>
	public static bool TryGetUnixMode(string path, out Mode mode)
	{
		if (stat(path, out var stat) == 0)
		{
			mode = (Mode)(stat.st_mode & 0xFFF);
			return true;
		}

		mode = 0;
		return false;
	}
}
#endif

テストコード兼使い方

#if UNITY_STANDALONE_OSX
using NUnit.Framework;

public class SysCallTest
{
	[Test]
	public void TestEtcPasswd1()
	{
		if (SysCall.TryGetUnixMode("/etc/passwd", out var mode))
		{
			Assert.AreEqual(mode, Mode.ReadUser | Mode.WriteUser | Mode.ReadGroup | Mode.ReadOther);
		}
		else
		{
			Assert.Fail();
		}
	}

	[Test]
	public void TestEtcPasswd2()
	{
		if (SysCall.TryGetUnixMode("/etc/passwd", out var mode))
		{
			Assert.AreEqual((int)mode, 0b_000_110_100_100);
		}
		else
		{
			Assert.Fail();
		}
	}

	[Test]
	public void TestShBin1()
	{
		if (SysCall.TryGetUnixMode("/bin/sh", out var mode))
		{
			Assert.AreEqual(mode,
				Mode.ReadUser | Mode.WriteUser | Mode.ExecuteUser |
				Mode.ReadGroup | Mode.ExecuteGroup |
				Mode.ReadOther | Mode.EcecuteOther);
		}
		else
		{
			Assert.Fail();
		}
	}

	[Test]
	public void TestShBin2()
	{
		if (SysCall.TryGetUnixMode("/bin/sh", out var mode))
		{
			Assert.AreEqual((int)mode, 0b_000_111_101_101);
		}
		else
		{
			Assert.Fail();
		}
	}
}
#endif

最後に

man 2 stat64 でstat構造体が分かりますが、 dev_tmode_t などの具体的な型が分からないため、find /Applications/Xcode.app -name stat.h -type f でXcodeのヘッダーファイルを探してすべての型を調べました。

Discussion