fn get_systemd_socket()

in http-common/src/connector.rs [730:824]


fn get_systemd_socket(
    socket_name: Option<String>,
) -> Result<Option<std::os::unix::io::RawFd>, String> {
    // Ref: <https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html>
    //
    // Try to find a systemd socket to match when non "fd" path has been provided.
    // We consider 4 cases:
    // 1. When there is only 1 socket. In this case, we can ignore the socket name. It means
    // the call is made by identity service which uses only one systemd socket. So matching is simple
    // 2. There are > 1 systemd sockets and a socket name is provided. It means edged is telling us to match an fd with the provided socket name.
    // 3. There are > 1 systemd sockets and a socket name is provided but no LISTEN_FDNAMES. We can't match.
    // 4. There are > 1 systemd sockets but no socket name is provided. In this case it means there is no corresponding systemd socket we should match
    //
    // >sd_listen_fds parses the number passed in the $LISTEN_FDS environment variable, then sets the FD_CLOEXEC flag
    // >for the parsed number of file descriptors starting from SD_LISTEN_FDS_START. Finally, it returns the parsed number.
    //
    // Note that it's not possible to distinguish between fd numbers if a process requires more than one socket.
    // That is why in edged's case we use the systemd socket name to know which fd the function should return
    // CS/IS/KS currently only expect one socket, so this is fine; but it is not the case for iotedged (mgmt and workload sockets)
    // for example.
    //
    // The complication with LISTEN_FDNAMES is that CentOS 7's systemd is too old and doesn't support it, which
    // would mean CS/IS/KS would have to stop using systemd socket activation on CentOS 7 (just like iotedged). This creates more complications,
    // because now the sockets either have to be placed in /var/lib/aziot (just like iotedged does) which means host modules need to try
    // both /run/aziot and /var/lib/aziot to connect to a service, or the services continue to bind sockets under /run/aziot but have to create
    // /run/aziot themselves on startup with ACLs for all three users and all three groups.

    let listen_fds: std::os::unix::io::RawFd = match get_env("LISTEN_FDS")? {
        Some(listen_fds) => listen_fds
            .parse()
            .map_err(|err| format!("could not read LISTEN_FDS env var: {err}"))?,

        None => return Ok(None),
    };

    // If there is no socket available, no match is possible.
    if listen_fds == 0 {
        return Ok(None);
    }

    // fcntl(CLOEXEC) all the fds so that they aren't inherited by the child processes.
    // Note that we want to do this for all the fds, not just the one we're looking for.
    for fd in SD_LISTEN_FDS_START..(SD_LISTEN_FDS_START + listen_fds) {
        if let Err(err) = nix::fcntl::fcntl(
            fd,
            nix::fcntl::FcntlArg::F_SETFD(nix::fcntl::FdFlag::FD_CLOEXEC),
        ) {
            return Err(format!("could not fcntl({fd}, F_SETFD, FD_CLOEXEC): {err}"));
        }
    }

    // If there is only one socket, we know this is the identity service which uses only one socket, so we have a match:
    if listen_fds == 1 {
        return Ok(Some(SD_LISTEN_FDS_START));
    }

    // If there is more than 1 socket and we don't have a socket name to match, this is edged telling us that there is no systemd socket we can match.
    let Some(socket_name) = socket_name else {
        return Ok(None);
    };

    // If there is more than one socket, this is edged. We can attempt to match the socket name to systemd.
    // This happens when a unix Uri is provided in the config.toml. Systemd sockets get created nonetheless, so we still prefer to use them.
    // If a socket name is provided but we don't see the env variable LISTEN_FDNAMES, it means we are probably on an older OS, and we can't match either.
    let Some(listen_fdnames) = get_env("LISTEN_FDNAMES")? else {
        return Ok(None);
    };
    let listen_fdnames: Vec<&str> = listen_fdnames.split(':').collect();

    let len: std::os::unix::io::RawFd = listen_fdnames
        .len()
        .try_into()
        .map_err(|_| "invalid number of sockets".to_string())?;
    if listen_fds != len {
        return Err(format!(
            "Mismatch, there are {} fds, and {} names",
            listen_fds,
            listen_fdnames.len()
        ));
    }

    if let Some(index) = listen_fdnames
        .iter()
        .position(|fdname| (*fdname).eq(&socket_name))
    {
        let index: std::os::unix::io::RawFd = index
            .try_into()
            .map_err(|_| "invalid number of sockets".to_string())?;
        Ok(Some(SD_LISTEN_FDS_START + index))
    } else {
        Err(format!(
            "Could not find a match for {socket_name} in the fd list"
        ))
    }
}