fix(core-update): sudo 재생성 시 public/storage 링크 소유자가 root 로 바뀌는 문제

sudo 로 실행된 종료 시점 복구가 symlink 로 public/storage 를 재생성하면
소유권 인자가 없어 링크가 root:root 로 남아, 원래 앱 실행 유저 소유였던
링크의 소유권이 바뀌었다. 소유권 복원(restoreOwnership) 이후 재생성되어
복원 대상에서도 빠졌다.

StorageLinkHelper::createLink 가 symlink 성공 직후 부모 public/ 의
owner/group 을 lchown/lchgrp(링크 자체 대상 — chown 은 target 을 따라감)로
상속시켜, 이 프로젝트의 "신규 항목은 부모 소유권 상속" 컨벤션과 일치시킨다.
비-POSIX/함수 부재/권한 부족 시 무해하게 skip.
This commit is contained in:
HeuJung committed 2026-07-16 14:28:34 +09:00
1 parent 0a6ff23132
commit f157ed1cc9
4 files changed
+89 -2

No files matched your search

@@ -117,6 +117,8 @@ class StorageLinkHelper
protected static function createLink(string $target, string $link): bool
{
if (@symlink($target, $link)) {
static::inheritParentOwnership($link);
return true;
}
@@ -135,6 +137,39 @@ class StorageLinkHelper
return false;
}
/**
* 갓 생성한 링크의 소유자/그룹을 부모 디렉토리로부터 상속시킵니다.
*
* `symlink()` 는 소유권 지정 인자가 없어 실행 프로세스(sudo → root)의 소유로 링크를
* 만든다. 이 때문에 sudo 로 실행된 코어 업데이트가 종료 시점에 `public/storage` 를
* 재생성하면 링크가 root:root 로 남아, 원래 앱 실행 유저 소유였던 링크의 소유권이
* 바뀌던 문제(#43 후속)를 차단한다. 부모 `public/` 의 owner/group 을 기준으로 삼아
* 이 프로젝트의 "신규 항목은 부모 소유권 상속" 컨벤션(`FilePermissionHelper`)과 일치시킨다.
*
* `chown`/`chgrp` 은 심볼릭 링크의 target 을 따라가므로, 링크 *자체* 를 대상으로 하는
* `lchown`/`lchgrp` 을 사용한다. 비-POSIX(Windows) 또는 함수 부재/권한 부족 시 무해하게 skip.
*
* @param string $link 방금 생성한 링크 경로
*/
protected static function inheritParentOwnership(string $link): void
{
// lchown/lchgrp 은 POSIX 전용 — Windows 및 함수 비활성 환경에서는 no-op.
if (! function_exists('lchown') || ! function_exists('lchgrp')) {
return;
}
$parent = dirname($link);
$owner = @fileowner($parent);
$group = @filegroup($parent);
if ($owner !== false) {
@lchown($link, $owner);
}
if ($group !== false) {
@lchgrp($link, $group);
}
}
/**
* 경로가 Windows JUNCTION 인지 판정합니다 (`is_link()` 미인식 reparse point).
*