Skip to content

Commit 2439a78

Browse files
Merge remote-tracking branch 'origin/main' into stlc/promote-next
2 parents efceb62 + b885bb2 commit 2439a78

4 files changed

Lines changed: 66 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ jobs:
3737
- name: Run lints
3838
run: ./scripts/lint
3939

40+
- name: Run tests
41+
run: ./scripts/test
42+
4043
build:
4144
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
4245
timeout-minutes: 10

bin/check-release-environment

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ fi
1212

1313
lenErrors=${#errors[@]}
1414

15-
if [[ lenErrors -gt 0 ]]; then
15+
if [[ "$lenErrors" -gt 0 ]]; then
1616
echo -e "Found the following errors in the release environment:\n"
1717

1818
for error in "${errors[@]}"; do

src/hypeman/lib/cp.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ class _UploadEntry:
5858
guest_path: str
5959
is_dir: bool
6060
mode: int
61-
uid: int
62-
gid: int
61+
uid: int | None
62+
gid: int | None
6363
size: int
6464

6565

@@ -91,8 +91,8 @@ def visit(local_path: Path, remote_path: str, root: bool = False) -> None:
9191

9292
is_dir = stat.S_ISDIR(info.st_mode)
9393
entry_mode = mode if root and mode is not None else stat.S_IMODE(info.st_mode)
94-
uid = int(getattr(info, "st_uid", 0)) if archive else 0
95-
gid = int(getattr(info, "st_gid", 0)) if archive else 0
94+
uid = int(getattr(info, "st_uid", 0)) if archive else None
95+
gid = int(getattr(info, "st_gid", 0)) if archive else None
9696
entries.append(
9797
_UploadEntry(
9898
source=local_path,
@@ -131,9 +131,9 @@ def _request(entry: _UploadEntry) -> str:
131131
"is_dir": entry.is_dir,
132132
"mode": entry.mode,
133133
}
134-
if entry.uid:
134+
if entry.uid is not None:
135135
payload["uid"] = entry.uid
136-
if entry.gid:
136+
if entry.gid is not None:
137137
payload["gid"] = entry.gid
138138
return json.dumps(payload, separators=(",", ":"))
139139

@@ -318,6 +318,7 @@ def __init__(self, destination: Path, archive: bool, callbacks: CopyCallbacks |
318318
self.file: BinaryIO | None = None
319319
self.temp_path: Path | None = None
320320
self.bytes_received = 0
321+
self.directories: list[_DownloadHeader] = []
321322
self.complete = False
322323

323324
def abort(self) -> None:
@@ -405,8 +406,7 @@ def _start(self, message: dict[str, object]) -> None:
405406
if target.is_symlink() or (target.exists() and not target.is_dir()):
406407
raise CopyProtocolError(f"cp cannot replace local path with directory: {target}")
407408
target.mkdir(parents=True, exist_ok=True)
408-
target.chmod(header.mode)
409-
self._chown(target, header, follow_symlinks=True)
409+
self.directories.append(header)
410410
elif header.is_symlink:
411411
self._create_symlink(header)
412412
else:
@@ -474,6 +474,11 @@ def _end(self, message: dict[str, object]) -> None:
474474
self.header = None
475475
self.bytes_received = 0
476476
if final:
477+
for directory in reversed(self.directories):
478+
if directory.mtime:
479+
os.utime(directory.target, (directory.mtime, directory.mtime))
480+
self._chown(directory.target, directory, follow_symlinks=True)
481+
directory.target.chmod(directory.mode)
477482
self.complete = True
478483

479484
def _chown(self, path: Path, header: _DownloadHeader, *, follow_symlinks: bool) -> None:

tests/lib/test_websocket_lib.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
cp_to_instance_async,
2323
cp_from_instance_async,
2424
)
25+
from hypeman.lib.cp import _request, _UploadEntry
2526

2627

2728
@dataclass
@@ -268,10 +269,8 @@ def test_cp_upload_file_preserves_mode_and_reports_progress(tmp_path: Path) -> N
268269
"mode": 0o640,
269270
}
270271
source_stat = source.stat()
271-
if source_stat.st_uid:
272-
expected_request["uid"] = source_stat.st_uid
273-
if source_stat.st_gid:
274-
expected_request["gid"] = source_stat.st_gid
272+
expected_request["uid"] = source_stat.st_uid
273+
expected_request["gid"] = source_stat.st_gid
275274
assert request == expected_request
276275
assert websocket.sent[1:] == [b"payload", '{"type":"end"}']
277276
assert connector.calls[0][2] == 2**20
@@ -282,6 +281,14 @@ def test_cp_upload_file_preserves_mode_and_reports_progress(tmp_path: Path) -> N
282281
]
283282

284283

284+
def test_cp_upload_archive_includes_root_ownership(tmp_path: Path) -> None:
285+
entry = _UploadEntry(tmp_path, "/guest/file", False, 0o644, 0, 0, 0)
286+
request = json.loads(_request(entry))
287+
288+
assert request["uid"] == 0
289+
assert request["gid"] == 0
290+
291+
285292
def test_cp_upload_directory_uses_one_connection_per_entry(tmp_path: Path) -> None:
286293
source = tmp_path / "tree"
287294
source.mkdir()
@@ -357,6 +364,44 @@ def test_cp_download_file_is_atomic_and_preserves_metadata(tmp_path: Path) -> No
357364
}
358365

359366

367+
def test_cp_download_defers_restrictive_directory_mode(tmp_path: Path) -> None:
368+
directory = text_frame(
369+
{
370+
"type": "header",
371+
"path": "tree",
372+
"mode": 0o500,
373+
"is_dir": True,
374+
"is_symlink": False,
375+
"link_target": "",
376+
"size": 0,
377+
"mtime": 0,
378+
}
379+
)
380+
websocket = FakeWebSocket(
381+
deque(
382+
[
383+
directory,
384+
text_frame({"type": "end", "final": False}),
385+
file_header("tree/child.txt", size=1),
386+
b"x",
387+
text_frame({"type": "end", "final": True}),
388+
]
389+
)
390+
)
391+
destination = tmp_path / "download"
392+
393+
cp_from_instance(
394+
FakeClient(),
395+
"inst",
396+
"/guest/tree",
397+
destination,
398+
connector=FakeConnector(deque([websocket])),
399+
)
400+
401+
assert (destination / "tree" / "child.txt").read_bytes() == b"x"
402+
assert stat_mode(destination / "tree") == 0o500
403+
404+
360405
def stat_mode(path: Path) -> int:
361406
return path.stat().st_mode & 0o7777
362407

0 commit comments

Comments
 (0)