Skip to content

Commit

Permalink
Change how the user is determined before creating a symlink
Browse files Browse the repository at this point in the history
- Rather than a filesystem path or environment variables, determine the user via the password database before creating a symbolic link to the steamuser directory -- this is how WINE does it

- See https://github.com/wine-mirror/wine/blob/master/dlls/ntdll/unix/loader.c#L384

- Related to #21 (comment)
  • Loading branch information
R1kaB3rN committed Feb 13, 2024
1 parent 4f64389 commit f507d4a
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 8 deletions.
38 changes: 35 additions & 3 deletions ulwgl_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ulwgl_plugins
from re import match
import subprocess
from ulwgl_util import UnixUser


def parse_args() -> Union[Namespace, Tuple[str, List[str]]]: # noqa: D103
Expand Down Expand Up @@ -43,14 +44,45 @@ def parse_args() -> Union[Namespace, Tuple[str, List[str]]]: # noqa: D103


def setup_pfx(path: str) -> None:
"""Create a symlink to the WINE prefix and tracked_files file."""
"""Create a symlink to the WINE prefix and tracked_files file.
Also, create a symlink of steamuser to the Unix username
"""
steam: Path = Path(path + "/drive_c/users/steamuser").expanduser()
uid: int = os.getuid()
user: UnixUser = UnixUser()
wineuser: Path = Path(path + "/drive_c/users/" + user.get_user()).expanduser()

if not (Path(path + "/pfx")).expanduser().is_symlink():
# When creating the symlink, we want it to be in expanded form when passed unexpanded paths
# Example: pfx -> /home/foo/.wine
# NOTE: When parsing a config file, an error can be raised if the prefix doesn't already exist
# Good: pfx -> /home/foo/.wine
# Bad: pfx -> ~/.wine
Path(path + "/pfx").expanduser().symlink_to(Path(path).expanduser())
Path(path + "/tracked_files").expanduser().touch()

# Create a symlink of the current user to the steamuser dir for Steam functionality
# Only create a symlink for the current user
if not wineuser.is_dir() and not steam.is_dir() and user.is_user(uid):
steam.mkdir(parents=True)
wineuser.symlink_to(steam)
elif wineuser.is_dir() and not steam.is_dir() and user.is_user(uid):
wineuser.rename(steam)
wineuser.symlink_to(steam)
elif (
not (wineuser.exists() or wineuser.is_symlink())
and steam.is_dir()
and user.is_user(uid)
):
wineuser.symlink_to(steam)
elif not user.is_user(uid):
print(
f"Unable to identify the current user.\nSymbolic link will not be created to path: {steam}"
)
else:
print(
f"Paths exist: {steam} and {wineuser}\nPlease consider merging {wineuser} to {steam}."
)


def check_env(
env: Dict[str, str], toml: Dict[str, Any] = None
Expand Down
126 changes: 121 additions & 5 deletions ulwgl_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,8 +866,111 @@ def test_set_env(self):
"Expected STEAM_COMPAT_MOUNTS to be set",
)

def test_setup_pfx_symlinks_steam_mv(self):
"""Test setup_pfx for symbolic link to steamuser.
Tests the case when the steamuser dir does not exist and user dir exists
In this case, rename the existing user dir to steamuser and create a new symlink to it
"""
result = None
pattern = r"^/home/[a-zA-Z]+"
unexpanded_path = re.sub(
pattern,
"~",
Path(
Path(self.test_file).cwd().as_posix() + "/" + self.test_file
).as_posix(),
)

# Create only the user dir
Path(
unexpanded_path + "/drive_c/users/" + Path().home().name
).expanduser().mkdir(parents=True, exist_ok=True)

result = ulwgl_run.setup_pfx(unexpanded_path)
self.assertIsNone(
result,
"Expected None when creating symbolic link to WINE prefix and tracked_files file",
)
self.assertTrue(
Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink"
)
self.assertTrue(
Path(self.test_file + "/tracked_files").is_file(),
"Expected tracked_files to be a file",
)
self.assertTrue(
Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink"
)
# Check if the symlink is in its unexpanded form
self.assertEqual(
Path(self.test_file + "/pfx").readlink().as_posix(),
Path(unexpanded_path).expanduser().as_posix(),
)
self.assertTrue(
Path(self.test_file + "/drive_c/users/steamuser").is_dir(),
"Expected steamuser to be created",
)
self.assertTrue(
Path(unexpanded_path + "/drive_c/users/" + Path().home().name)
.expanduser()
.is_symlink(),
"Expected symlink of username -> steamuser",
)

def test_setup_pfx_symlinks_steam(self):
"""Test setup_pfx for symbolic link to steamuser.
Tests the case when the steamuser exists and a user does not exists
"""
result = None
pattern = r"^/home/[a-zA-Z]+"
unexpanded_path = re.sub(
pattern,
"~",
Path(
Path(self.test_file).cwd().as_posix() + "/" + self.test_file
).as_posix(),
)

# Create only the user dir
Path(unexpanded_path + "/drive_c/users/steamuser").expanduser().mkdir(
parents=True, exist_ok=True
)
result = ulwgl_run.setup_pfx(unexpanded_path)

self.assertIsNone(
result,
"Expected None when creating symbolic link to WINE prefix and tracked_files file",
)
self.assertTrue(
Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink"
)
self.assertTrue(
Path(self.test_file + "/tracked_files").is_file(),
"Expected tracked_files to be a file",
)
self.assertTrue(
Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink"
)
# Check if the symlink is in its unexpanded form
self.assertEqual(
Path(self.test_file + "/pfx").readlink().as_posix(),
Path(unexpanded_path).expanduser().as_posix(),
)
self.assertTrue(
Path(self.test_file + "/drive_c/users/steamuser").is_dir(),
"Expected steamuser to be created",
)
self.assertTrue(
Path(unexpanded_path + "/drive_c/users/" + Path().home().name)
.expanduser()
.is_symlink(),
"Expected symlink of username -> steamuser",
)

def test_setup_pfx_symlinks(self):
"""Test _setup_pfx for valid symlinks.
"""Test setup_pfx for valid symlinks.
Ensure that symbolic links to the WINE prefix (pfx) are always in expanded form when passed an unexpanded path.
For example:
Expand All @@ -879,6 +982,9 @@ def test_setup_pfx_symlinks(self):
"""
result = None
pattern = r"^/home/[a-zA-Z]+"

# Replaces the expanded path to unexpanded
# Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file
unexpanded_path = re.sub(
pattern,
"~",
Expand All @@ -888,8 +994,6 @@ def test_setup_pfx_symlinks(self):
)
result = ulwgl_run.setup_pfx(unexpanded_path)

# Replaces the expanded path to unexpanded
# Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file
self.assertIsNone(
result,
"Expected None when creating symbolic link to WINE prefix and tracked_files file",
Expand Down Expand Up @@ -918,15 +1022,16 @@ def test_setup_pfx_paths(self):
"""
result = None
pattern = r"^/home/[a-zA-Z]+"

# Replaces the expanded path to unexpanded
# Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file
unexpanded_path = re.sub(
pattern,
"~",
Path(Path(self.test_file).as_posix()).as_posix(),
)
result = ulwgl_run.setup_pfx(unexpanded_path)

# Replaces the expanded path to unexpanded
# Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file
self.assertIsNone(
result,
"Expected None when creating symbolic link to WINE prefix and tracked_files file",
Expand Down Expand Up @@ -954,6 +1059,17 @@ def test_setup_pfx(self):
Path(self.test_file + "/tracked_files").is_file(),
"Expected tracked_files to be a file",
)
# For new prefixes, steamuser should exist and a user symlink
self.assertTrue(
Path(self.test_file + "/drive_c/users/steamuser").is_dir(),
"Expected steamuser to be created",
)
self.assertTrue(
Path(self.test_file + "/drive_c/users/" + Path().home().name)
.expanduser()
.is_symlink(),
"Expected symlink of username -> steamuser",
)

def test_parse_args(self):
"""Test parse_args with no options.
Expand Down
32 changes: 32 additions & 0 deletions ulwgl_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
import pwd
from pathlib import Path
import typing
from pwd import struct_passwd

class UnixUser:
"""Represents the User of the system as determined by the password database rather than environment variables or file system paths."""

def __init__(self):
entry: struct_passwd = pwd.getpwuid(os.getuid())
# Immutable properties, hence no setters
self.name: str = entry.pw_name
self.puid: str = entry.pw_uid # Should be equivalent to the value from getuid
self.dir: str = entry.pw_dir

def get_home_dir(self) -> Path:
"""The User home directory as determined by the password database that's derived from the current process's real user id."""
return Path(self.dir).as_posix()

def get_user(self) -> str:
"""The User (login name) as determined by the password database that's derived from the current process's real user id."""
return self.name

def get_puid(self) -> int:
"""The numerical user ID as determined by the password database that's derived from the current process's real user id."""
return self.puid

def is_user(self, uid: int) -> bool:
"""Compare the UID passed in to this instance's"""
return uid == self.puid

0 comments on commit f507d4a

Please sign in to comment.