LinuxIsoManager.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. using System;
  2. using System.Diagnostics;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Runtime.InteropServices;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Model.IO;
  9. using MediaBrowser.Model.System;
  10. using Microsoft.Extensions.Logging;
  11. using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
  12. namespace IsoMounter
  13. {
  14. /// <summary>
  15. /// The ISO manager implementation for Linux.
  16. /// </summary>
  17. public class LinuxIsoManager : IIsoMounter
  18. {
  19. private const string MountCommand = "mount";
  20. private const string UnmountCommand = "umount";
  21. private const string SudoCommand = "sudo";
  22. private readonly ILogger _logger;
  23. private readonly string _mountPointRoot;
  24. /// <summary>
  25. /// Initializes a new instance of the <see cref="LinuxIsoManager" /> class.
  26. /// </summary>
  27. /// <param name="logger">The logger.</param>
  28. public LinuxIsoManager(ILogger logger)
  29. {
  30. _logger = logger;
  31. _mountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
  32. _logger.LogDebug(
  33. "[{0}] System PATH is currently set to [{1}].",
  34. Name,
  35. Environment.GetEnvironmentVariable("PATH") ?? string.Empty);
  36. _logger.LogDebug(
  37. "[{0}] System path separator is [{1}].",
  38. Name,
  39. Path.PathSeparator);
  40. _logger.LogDebug(
  41. "[{0}] Mount point root is [{1}].",
  42. Name,
  43. _mountPointRoot);
  44. }
  45. /// <inheritdoc />
  46. public string Name => "LinuxMount";
  47. #pragma warning disable SA1300
  48. #pragma warning disable SA1400
  49. [DllImport("libc", SetLastError = true)]
  50. static extern uint getuid();
  51. #pragma warning restore SA1300
  52. #pragma warning restore SA1400
  53. /// <inheritdoc />
  54. public bool CanMount(string path)
  55. {
  56. if (OperatingSystem.Id != OperatingSystemId.Linux)
  57. {
  58. return false;
  59. }
  60. _logger.LogInformation(
  61. "[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}].",
  62. Name,
  63. path,
  64. Path.GetExtension(path),
  65. OperatingSystem.Name);
  66. return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase);
  67. }
  68. /// <inheritdoc />
  69. public Task<IIsoMount> Mount(string isoPath, CancellationToken cancellationToken)
  70. {
  71. string cmdArguments;
  72. string cmdFilename;
  73. string mountPoint = Path.Combine(_mountPointRoot, Guid.NewGuid().ToString());
  74. if (string.IsNullOrEmpty(isoPath))
  75. {
  76. throw new ArgumentNullException(nameof(isoPath));
  77. }
  78. _logger.LogInformation(
  79. "[{Name}] Attempting to mount [{Path}].",
  80. Name,
  81. isoPath);
  82. _logger.LogDebug(
  83. "[{Name}] ISO will be mounted at [{Path}].",
  84. Name,
  85. mountPoint);
  86. try
  87. {
  88. Directory.CreateDirectory(mountPoint);
  89. }
  90. catch (UnauthorizedAccessException ex)
  91. {
  92. throw new IOException("Unable to create mount point(Permission denied) for " + isoPath, ex);
  93. }
  94. catch (Exception ex)
  95. {
  96. throw new IOException("Unable to create mount point for " + isoPath, ex);
  97. }
  98. if (GetUID() == 0)
  99. {
  100. cmdFilename = MountCommand;
  101. cmdArguments = string.Format(
  102. CultureInfo.InvariantCulture,
  103. "\"{0}\" \"{1}\"",
  104. isoPath,
  105. mountPoint);
  106. }
  107. else
  108. {
  109. cmdFilename = SudoCommand;
  110. cmdArguments = string.Format(
  111. CultureInfo.InvariantCulture,
  112. "\"{0}\" \"{1}\" \"{2}\"",
  113. MountCommand,
  114. isoPath,
  115. mountPoint);
  116. }
  117. _logger.LogDebug(
  118. "[{0}] Mount command [{1}], mount arguments [{2}].",
  119. Name,
  120. cmdFilename,
  121. cmdArguments);
  122. int exitcode = ExecuteCommand(cmdFilename, cmdArguments);
  123. if (exitcode == 0)
  124. {
  125. _logger.LogInformation(
  126. "[{0}] ISO mount completed successfully.",
  127. Name);
  128. return Task.FromResult<IIsoMount>(new LinuxMount(this, isoPath, mountPoint));
  129. }
  130. _logger.LogInformation(
  131. "[{0}] ISO mount completed with errors.",
  132. Name);
  133. try
  134. {
  135. Directory.Delete(mountPoint, false);
  136. }
  137. catch (Exception ex)
  138. {
  139. _logger.LogError(ex, "[{Name}] Unhandled exception removing mount point.", Name);
  140. throw;
  141. }
  142. throw new ExternalException("Mount command failed", exitcode);
  143. }
  144. private uint GetUID()
  145. {
  146. var uid = getuid();
  147. _logger.LogDebug(
  148. "[{0}] GetUserId() returned [{2}].",
  149. Name,
  150. uid);
  151. return uid;
  152. }
  153. private int ExecuteCommand(string cmdFilename, string cmdArguments)
  154. {
  155. var startInfo = new ProcessStartInfo
  156. {
  157. FileName = cmdFilename,
  158. Arguments = cmdArguments,
  159. UseShellExecute = false,
  160. CreateNoWindow = true,
  161. ErrorDialog = false,
  162. RedirectStandardOutput = true,
  163. RedirectStandardError = true
  164. };
  165. var process = new Process()
  166. {
  167. StartInfo = startInfo
  168. };
  169. try
  170. {
  171. process.Start();
  172. _logger.LogDebug(
  173. "[{Name}] Standard output from process is [{Error}].",
  174. Name,
  175. process.StandardOutput.ReadToEnd());
  176. _logger.LogDebug(
  177. "[{Name}] Standard error from process is [{Error}].",
  178. Name,
  179. process.StandardError.ReadToEnd());
  180. return process.ExitCode;
  181. }
  182. catch (Exception ex)
  183. {
  184. _logger.LogDebug(ex, "[{Name}] Unhandled exception executing command.", Name);
  185. throw;
  186. }
  187. finally
  188. {
  189. process?.Dispose();
  190. }
  191. }
  192. /// <summary>
  193. /// Unmounts the specified mount.
  194. /// </summary>
  195. /// <param name="mount">The mount.</param>
  196. internal void OnUnmount(LinuxMount mount)
  197. {
  198. if (mount == null)
  199. {
  200. throw new ArgumentNullException(nameof(mount));
  201. }
  202. _logger.LogInformation(
  203. "[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].",
  204. Name,
  205. mount.IsoPath,
  206. mount.MountedPath);
  207. string cmdArguments;
  208. string cmdFilename;
  209. if (GetUID() == 0)
  210. {
  211. cmdFilename = UnmountCommand;
  212. cmdArguments = string.Format(
  213. CultureInfo.InvariantCulture,
  214. "\"{0}\"",
  215. mount.MountedPath);
  216. }
  217. else
  218. {
  219. cmdFilename = SudoCommand;
  220. cmdArguments = string.Format(
  221. CultureInfo.InvariantCulture,
  222. "\"{0}\" \"{1}\"",
  223. UnmountCommand,
  224. mount.MountedPath);
  225. }
  226. _logger.LogDebug(
  227. "[{0}] Umount command [{1}], umount arguments [{2}].",
  228. Name,
  229. cmdFilename,
  230. cmdArguments);
  231. int exitcode = ExecuteCommand(cmdFilename, cmdArguments);
  232. if (exitcode == 0)
  233. {
  234. _logger.LogInformation(
  235. "[{0}] ISO unmount completed successfully.",
  236. Name);
  237. }
  238. else
  239. {
  240. _logger.LogInformation(
  241. "[{0}] ISO unmount completed with errors.",
  242. Name);
  243. }
  244. try
  245. {
  246. Directory.Delete(mount.MountedPath, false);
  247. }
  248. catch (Exception ex)
  249. {
  250. _logger.LogError(ex, "[{Name}] Unhandled exception removing mount point.", Name);
  251. throw;
  252. }
  253. throw new ExternalException("Mount command failed", exitcode);
  254. }
  255. }
  256. }