Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,8 @@ fn patch(path: PathBuf) -> PathBuf {
#[cfg(target_os = "linux")]
{
if _tmp == "/root" {
if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") {
if user != "root" {
let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user);
if let Ok(output) = crate::platform::linux::run_cmds_trim_newline(&cmd) {
return output.into();
}
return format!("/home/{user}").into();
}
if let Some(home_dir) = std::env::home_dir() {
return home_dir;
Comment on lines +443 to +444
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The std::env::home_dir() function has been deprecated since Rust 1.29 (2018) due to incorrect behavior on Windows and platform inconsistencies. The deprecation notice recommends using the dirs or dirs_next crate instead. This codebase already uses dirs_next::home_dir() in other places (lines 628, 667), so you should use the same function here for consistency and to avoid using deprecated APIs.

Copilot uses AI. Check for mistakes.
}
}
}
Expand Down
77 changes: 23 additions & 54 deletions src/platform/linux.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::ResultType;
use std::{collections::HashMap, process::Command};
use std::{collections::HashMap, io::BufRead, process::Command};

use sctk::{
output::OutputData,
Expand Down Expand Up @@ -37,8 +37,6 @@ lazy_static::lazy_static! {
// ...
lazy_static::lazy_static! {
pub static ref CMD_LOGINCTL: String = find_cmd_path("loginctl");
pub static ref CMD_PS: String = find_cmd_path("ps");
pub static ref CMD_SH: String = find_cmd_path("sh");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I was working on this repository alone, and didn’t notice it was API for rustdesk, my bad! I’ll put all of the pub symbols back to their former state then, and also do the same kind of fixes in rustdesk, thanks!

}

pub const DISPLAY_SERVER_WAYLAND: &str = "wayland";
Expand All @@ -54,17 +52,24 @@ pub struct Distro {

impl Distro {
fn new() -> Self {
let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release")
.unwrap_or_default()
.trim()
.trim_matches('"')
.to_string();
let version_id = run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release")
.unwrap_or_default()
.trim()
.trim_matches('"')
.to_string();
Self { name, version_id }
let mut name = None;
let mut version_id = None;
if let Ok(os_release) = std::fs::File::open("/etc/os-release") {
let os_release = std::io::BufReader::new(os_release);
for line in os_release.lines() {
if let Some((key, value)) = line.unwrap_or_default().split_once('=') {
if key == "NAME" {
name = Some(value.trim_matches('"').to_owned());
} else if key == "VERSION_ID" {
version_id = Some(value.trim_matches('"').to_owned());
Comment on lines +60 to +64
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line.unwrap_or_default() call silently ignores I/O errors when reading lines from the file. If a line read fails, it will be treated as an empty string, potentially skipping valid data. Consider using a pattern that either propagates errors or handles them explicitly, such as:

if let Ok(line_content) = line {
    if let Some((key, value)) = line_content.split_once('=') {
        // process key-value pairs
    }
}
Suggested change
if let Some((key, value)) = line.unwrap_or_default().split_once('=') {
if key == "NAME" {
name = Some(value.trim_matches('"').to_owned());
} else if key == "VERSION_ID" {
version_id = Some(value.trim_matches('"').to_owned());
if let Ok(line_content) = line {
if let Some((key, value)) = line_content.split_once('=') {
if key == "NAME" {
name = Some(value.trim_matches('"').to_owned());
} else if key == "VERSION_ID" {
version_id = Some(value.trim_matches('"').to_owned());
}

Copilot uses AI. Check for mistakes.
}
}
}
}
Self {
name: name.unwrap_or_default(),
version_id: version_id.unwrap_or_default(),
}
}
}

Expand Down Expand Up @@ -99,9 +104,9 @@ pub fn is_kde() -> bool {
// Don't use `hbb_common::platform::linux::is_kde()` here.
// It's not correct in the server process.
pub fn is_kde_session() -> bool {
std::process::Command::new(CMD_SH.as_str())
.arg("-c")
.arg("pgrep -f kded[0-9]+")
Command::new("pgrep")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly removing CMD_SH.as_str() may generate an excessive number of audit logs.

// `ausearch -x /usr/share/rustdesk/rustdesk` will return

ausearch -x /usr/share/rustdesk/rustdesk

This is because the audit log will record ENOENT errors when the system attempts to resolve the absolute path of the command.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a better change could be

  pub fn is_kde_session() -> bool {
      let Ok(entries) = fs::read_dir("/proc") else {
          return false;
      };

      for entry in entries.flatten() {
          let name = entry.file_name();
          let name_str = name.to_string_lossy();

          // Skip non-pid directories
          if !name_str.chars().all(|c| c.is_ascii_digit()) {
              continue;
          }

          // Read /proc/[pid]/comm (process name, max 16 chars, no args)
          let comm_path = entry.path().join("comm");
          if let Ok(comm) = fs::read_to_string(&comm_path) {
              let comm = comm.trim();
              // Match kded5, kded6, etc.
              if comm.starts_with("kded") && comm[4..].chars().all(|c| c.is_ascii_digit()) {
                  return true;
              }
          }
      }
      false
  }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed that’s much better, I’ll update that PR with your code. Using shell commands for pretty much anything is a red flag anyway, silencing the audit logs was always a bad workaround.

.arg("-f")
.arg("kded[0-9]+")
.stdout(std::process::Stdio::piped())
.output()
.map(|o| !o.stdout.is_empty())
Expand Down Expand Up @@ -146,7 +151,7 @@ pub fn get_display_server() -> String {
session = sid;
}
if session.is_empty() {
session = run_cmds("cat /proc/self/sessionid").unwrap_or_default();
session = std::fs::read_to_string("/proc/self/sessionid").unwrap_or_default();
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The std::fs::read_to_string() call includes any trailing newline characters from the file. The previous cat /proc/self/sessionid command through the shell would have included the newline, but the comparison with INVALID_SESSION on line 155 might now fail if the file content has trailing whitespace. Consider trimming the result: std::fs::read_to_string("/proc/self/sessionid").unwrap_or_default().trim().to_string()

Suggested change
session = std::fs::read_to_string("/proc/self/sessionid").unwrap_or_default();
session = std::fs::read_to_string("/proc/self/sessionid").unwrap_or_default().trim().to_string();

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file can’t ever contain any newline, it can only be a decimal integer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thinks it's better to always trim() here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, this is ABI, the kernel will never ever add a newline or any other character here, so trim() will always be a noop.

That said, in a future PR I have already started working on, this whole mess of fetching the session is simplified by always using logind using zbus.

if session == INVALID_SESSION {
session = "".to_owned();
}
Expand Down Expand Up @@ -280,27 +285,6 @@ pub fn is_session_locked(sid: &str) -> bool {
}
}

// **Note** that the return value here, the last character is '\n'.
// Use `run_cmds_trim_newline()` if you want to remove '\n' at the end.
pub fn run_cmds(cmds: &str) -> ResultType<String> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run_cmds() is commonly used in https://github.com/rustdesk/rustdesk

Maybe we need to keep it in this PR.

let output = std::process::Command::new(CMD_SH.as_str())
.args(vec!["-c", cmds])
.output()?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}

pub fn run_cmds_trim_newline(cmds: &str) -> ResultType<String> {
let output = std::process::Command::new(CMD_SH.as_str())
.args(vec!["-c", cmds])
.output()?;
let out = String::from_utf8_lossy(&output.stdout);
Ok(if out.ends_with('\n') {
out[..out.len() - 1].to_string()
} else {
out.to_string()
})
}

fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
if std::env::var("FLATPAK_ID").is_ok() {
let mut l_args = CMD_LOGINCTL.to_string();
Expand Down Expand Up @@ -441,18 +425,3 @@ pub fn get_wayland_displays() -> ResultType<Vec<WaylandDisplayInfo>> {

Ok(display_infos)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_run_cmds_trim_newline() {
assert_eq!(run_cmds_trim_newline("echo -n 123").unwrap(), "123");
assert_eq!(run_cmds_trim_newline("echo 123").unwrap(), "123");
assert_eq!(
run_cmds_trim_newline("whoami").unwrap() + "\n",
run_cmds("whoami").unwrap()
);
}
}