File: SystemdHelpers.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.Hosting.Systemd\src\Microsoft.Extensions.Hosting.Systemd.csproj (Microsoft.Extensions.Hosting.Systemd)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Globalization;
using System.IO;
using System.Text;
 
namespace Microsoft.Extensions.Hosting.Systemd
{
    /// <summary>
    /// Helper methods for systemd Services.
    /// </summary>
    public static class SystemdHelpers
    {
        private static readonly bool _isSystemdService = GetIsSystemdService();
 
        /// <summary>
        /// Checks if the current process is hosted as a systemd Service.
        /// </summary>
        /// <returns>
        /// <see langword="true" /> if the current process is hosted as a systemd Service; otherwise, <see langword="false" />.
        /// </returns>
        public static bool IsSystemdService() => _isSystemdService;
 
        private static bool GetIsSystemdService()
        {
            // No point in testing anything unless it's Unix
            if (Environment.OSVersion.Platform != PlatformID.Unix)
            {
                return false;
            }
 
            int processId = Environment.ProcessId;
 
            // Preferred detection method: compare SYSTEMD_EXEC_PID to the current PID.
            // Works even when ProtectProc=invisible hides /proc entries.
            string? systemdExecPid = Environment.GetEnvironmentVariable(SystemdConstants.SystemdExecPid);
            if (!string.IsNullOrEmpty(systemdExecPid))
            {
                if (int.TryParse(systemdExecPid, NumberStyles.None, CultureInfo.InvariantCulture, out int execPid))
                {
                    if (execPid == processId)
                    {
                        return true;
                    }
                    // Mismatch: fall through to PID 1 / legacy checks
                }
                // Malformed value: don't trust it, fall through to legacy detection.
            }
 
            // To support containerized systemd services (e.g. Podman with --sdnotify=container)
            if (processId == 1)
            {
                return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(SystemdConstants.NotifySocket)) ||
                       !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(SystemdConstants.ListenPid));
            }
 
            // Legacy detection for systemd < 248 (e.g. Ubuntu 20.04, Debian 11).
            // Note: silently returns false if /proc cannot be read, such as when ProtectProc=invisible is set.
            try
            {
                // Check whether our direct parent is 'systemd'.
                int parentPid = Interop.libc.GetParentPid();
                string ppidString = parentPid.ToString(NumberFormatInfo.InvariantInfo);
                byte[] comm = File.ReadAllBytes("/proc/" + ppidString + "/comm");
                return comm.AsSpan().SequenceEqual("systemd\n"u8);
            }
            catch
            {
            }
 
            return false;
        }
 
        private static readonly bool _isSystemdNotify = GetIsSystemdNotify();
 
        /// <summary>
        /// Checks if the current process has systemd notify enabled.
        /// </summary>
        private static bool IsSystemdNotify() => _isSystemdNotify;
 
        private static bool GetIsSystemdNotify()
        {
            // No point in testing anything unless it's Unix
            if (Environment.OSVersion.Platform != PlatformID.Unix)
            {
                return false;
            }
            // Checks whether NOTIFY_SOCKET is set, indicating the service manager expects sd_notify notifications.
            string? socketPath = Environment.GetEnvironmentVariable(SystemdConstants.NotifySocket);
            return !string.IsNullOrEmpty(socketPath);
        }
 
        /// <summary>
        /// Checks if the systemd journal log formatter should be enabled.
        /// </summary>
        // TODO: #127218
        internal static bool IsSystemdLogger() => IsSystemdService();
 
        /// <summary>
        /// Checks if <see cref="SystemdLifetime"/> and <see cref="SystemdNotifier"/> should be registered.
        /// </summary>
        /// <remarks><see cref="SystemdNotifier"/> is a noop when <c>NOTIFY_SOCKET</c> is absent.</remarks>
        internal static bool IsSystemdLifetime() => IsSystemdService() || IsSystemdNotify();
    }
}