Skip to content

Commit 187d5be

Browse files
committed
Handle direct home mounts without symlink indirection
1 parent e333eff commit 187d5be

File tree

3 files changed

+140
-118
lines changed

3 files changed

+140
-118
lines changed

scripts/entrypoint.sh

Lines changed: 87 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ set -euo pipefail
1010
# - AICAGE_HOME: posix host home path
1111
# - AICAGE_HOST_IS_LINUX: set to non-empty on Linux hosts; empty otherwise
1212

13-
AICAGE_USER_HOME_MOUNTS_DIR="/aicage/user-home"
14-
1513
AICAGE_WORKSPACE="${AICAGE_WORKSPACE:-/workspace}"
1614

1715
if [[ -z "${AICAGE_HOST_IS_LINUX:-}" ]]; then
@@ -40,32 +38,6 @@ is_mountpoint() {
4038
[ "$(stat -c %d "$path" 2>/dev/null)" != "$(stat -c %d "$parent" 2>/dev/null)" ]
4139
}
4240

43-
is_within_mountpoint() {
44-
local path="$1"
45-
local mountinfo mount_point
46-
mountinfo="/proc/self/mountinfo"
47-
if [ -r "${mountinfo}" ]; then
48-
while IFS= read -r mount_point; do
49-
if [[ "${path}" == "${mount_point}" || "${path}" == "${mount_point}/"* ]]; then
50-
return 0
51-
fi
52-
done < <(awk '{print $5}' "${mountinfo}")
53-
return 1
54-
fi
55-
local current
56-
current="$path"
57-
while true; do
58-
if [ -e "$current" ] && is_mountpoint "$current"; then
59-
return 0
60-
fi
61-
if [ "$current" = "/" ]; then
62-
break
63-
fi
64-
current="$(dirname "$current")"
65-
done
66-
return 1
67-
}
68-
6941
ensure_home_is_not_mounted() {
7042
local path="$1"
7143
local current
@@ -82,28 +54,6 @@ ensure_home_is_not_mounted() {
8254
done
8355
}
8456

85-
replace_symlink() {
86-
local target_path="$1"
87-
local link_path="$2"
88-
local resolved_path
89-
90-
resolved_path="$link_path"
91-
if [[ -e "${link_path}" || -L "${link_path}" ]]; then
92-
resolved_path="$(readlink -f "${link_path}" 2>/dev/null || printf "%s" "${link_path}")"
93-
fi
94-
if is_within_mountpoint "$resolved_path"; then
95-
return 0
96-
fi
97-
98-
if [[ -e "${link_path}" || -L "${link_path}" ]]; then
99-
local timestamp backup_path
100-
timestamp="$(date +%Y%m%d%H%M%S%N)"
101-
backup_path="${link_path}.${timestamp}"
102-
mv "${link_path}" "${backup_path}"
103-
fi
104-
ln -sfn "${target_path}" "${link_path}"
105-
}
106-
10757
copy_skel_if_safe() {
10858
local home_dir="$1"
10959
local uid="$2"
@@ -141,34 +91,92 @@ set_target_env() {
14191
}
14292

14393
list_home_mount_points() {
144-
local mountinfo
94+
local mountinfo line mount_point
14595
mountinfo="/proc/self/mountinfo"
14696
[ -r "${mountinfo}" ] || return 0
147-
# /proc/self/mountinfo format:
148-
# field 5 is the mount point path
149-
# we only want mount points under /aicage/user-home
150-
awk -v base="${AICAGE_USER_HOME_MOUNTS_DIR}" '$5 ~ "^"base {print $5}' "${mountinfo}"
97+
98+
while IFS= read -r line; do
99+
set -- ${line}
100+
mount_point="$5"
101+
if [[ "${mount_point}" == "${AICAGE_HOME}" || "${mount_point}" == "${AICAGE_HOME}/"* ]]; then
102+
printf '%s\n' "${mount_point}"
103+
fi
104+
done < "${mountinfo}" | sort -u
151105
}
152106

153-
setup_home_mount_links() {
154-
local rel_path mount_point host_link root_link
155-
[ -d "${AICAGE_USER_HOME_MOUNTS_DIR}" ] || return 0
107+
normalize_mount_path() {
108+
local path="$1"
109+
if [ -d "${path}" ]; then
110+
printf '%s/\n' "${path%/}"
111+
else
112+
printf '%s\n' "${path}"
113+
fi
114+
}
156115

157-
while IFS= read -r mount_point; do
158-
if [[ "${mount_point}" == "${AICAGE_USER_HOME_MOUNTS_DIR}" ]]; then
159-
continue
160-
fi
161-
rel_path="${mount_point#${AICAGE_USER_HOME_MOUNTS_DIR}/}"
162-
host_link="${AICAGE_HOME}/${rel_path}"
163-
mkdir -p "$(dirname "${host_link}")"
164-
replace_symlink "${mount_point}" "${host_link}"
165-
166-
if [[ -z "${AICAGE_HOST_IS_LINUX:-}" ]]; then
167-
root_link="${TARGET_HOME}/${rel_path}"
168-
mkdir -p "$(dirname "${root_link}")"
169-
replace_symlink "${mount_point}" "${root_link}"
116+
filter_nested_mount_points() {
117+
local i j
118+
local is_nested
119+
local -a mount_points
120+
121+
mount_points=("$@")
122+
for i in "${!mount_points[@]}"; do
123+
mount_points[i]="$(normalize_mount_path "${mount_points[i]}")"
124+
done
125+
126+
for i in "${!mount_points[@]}"; do
127+
[ -n "${mount_points[i]}" ] || continue
128+
is_nested=0
129+
for j in "${!mount_points[@]}"; do
130+
[ -n "${mount_points[j]}" ] || continue
131+
if [[ "${i}" -eq "${j}" ]]; then
132+
continue
133+
fi
134+
if [[ "${mount_points[i]}" == "${mount_points[j]}"* ]]; then
135+
is_nested=1
136+
break
137+
fi
138+
done
139+
if [[ "${is_nested}" -eq 0 ]]; then
140+
printf '%s\n' "${mount_points[i]}"
170141
fi
171-
done < <(list_home_mount_points)
142+
done
143+
}
144+
145+
ensure_home_mount_parents_owned() {
146+
local uid="$1"
147+
local gid="$2"
148+
local current mount_point
149+
local -a mount_points mount_points_filtered
150+
local -A visited_dirs
151+
mapfile -t mount_points < <(list_home_mount_points)
152+
mapfile -t mount_points_filtered < <(filter_nested_mount_points "${mount_points[@]}")
153+
visited_dirs=()
154+
155+
if [ -d "${AICAGE_HOME}" ] && ! is_mountpoint "${AICAGE_HOME}"; then
156+
chown "${uid}:${gid}" "${AICAGE_HOME}"
157+
fi
158+
159+
for mount_point in "${mount_points_filtered[@]}"; do
160+
current="$(dirname "${mount_point}")"
161+
while [[ "${current}" == "${AICAGE_HOME}" || "${current}" == "${AICAGE_HOME}/"* ]]; do
162+
if [[ "${current}" == "${AICAGE_HOME}" ]]; then
163+
break
164+
fi
165+
if [[ -n "${visited_dirs[$current]:-}" ]]; then
166+
current="$(dirname "${current}")"
167+
continue
168+
fi
169+
visited_dirs["$current"]=1
170+
if is_mountpoint "${current}"; then
171+
current="$(dirname "${current}")"
172+
continue
173+
fi
174+
if [ -d "${current}" ]; then
175+
chown "${uid}:${gid}" "${current}"
176+
fi
177+
current="$(dirname "${current}")"
178+
done
179+
done
172180
}
173181

174182
setup_user_and_group() {
@@ -193,7 +201,11 @@ setup_user_and_group() {
193201
groupadd -g "${AICAGE_GID}" "${TARGET_USER}"
194202
fi
195203

196-
useradd --create-home -u "${AICAGE_UID}" -g "${AICAGE_GID}" -d "${AICAGE_HOME}" -s /bin/bash "${TARGET_USER}"
204+
if [[ -d "${AICAGE_HOME}" ]]; then
205+
useradd --no-create-home -u "${AICAGE_UID}" -g "${AICAGE_GID}" -d "${AICAGE_HOME}" -s /bin/bash "${TARGET_USER}"
206+
else
207+
useradd --create-home -u "${AICAGE_UID}" -g "${AICAGE_GID}" -d "${AICAGE_HOME}" -s /bin/bash "${TARGET_USER}"
208+
fi
197209
TARGET_HOME="${AICAGE_HOME}"
198210

199211
copy_skel_if_safe "${AICAGE_HOME}" "${AICAGE_UID}" "${AICAGE_GID}"
@@ -224,14 +236,10 @@ setup_docker_group() {
224236
}
225237

226238
setup_workspace() {
227-
if [ -e "${AICAGE_WORKSPACE}" ]; then
239+
if [ -e "${AICAGE_WORKSPACE}" ] && ! is_mountpoint "${AICAGE_WORKSPACE}"; then
228240
chown "${AICAGE_UID}:${AICAGE_GID}" "${AICAGE_WORKSPACE}"
229241
fi
230-
if [ -d "${TARGET_HOME}" ]; then
231-
if ! is_mountpoint "/home" && ! is_mountpoint "${TARGET_HOME}"; then
232-
chown "${AICAGE_UID}:${AICAGE_GID}" "${TARGET_HOME}"
233-
fi
234-
fi
242+
ensure_home_mount_parents_owned "${AICAGE_UID}" "${AICAGE_GID}"
235243
}
236244

237245
ensure_home_is_not_mounted "/home"
@@ -247,7 +255,6 @@ else
247255
fi
248256

249257
ensure_home_is_not_mounted "${AICAGE_HOME}"
250-
setup_home_mount_links
251258
set_target_env "${TARGET_HOME}" "${TARGET_USER}"
252259

253260
if [[ ! -e "${AICAGE_WORKSPACE}" ]]; then

tests/smoke/default/90-entrypoint-user.bats

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,29 @@
8484
[ "${user}" = "root" ]
8585
[ "${home}" = "/root" ]
8686
}
87+
88+
@test "existing home directory is reused for target user" {
89+
run docker run --rm \
90+
--env AICAGE_WORKSPACE=/workspace \
91+
--entrypoint /bin/bash \
92+
--env AICAGE_HOST_IS_LINUX=true \
93+
--env AICAGE_UID=2234 \
94+
--env AICAGE_GID=3234 \
95+
--env AICAGE_HOST_USER=demo \
96+
--env AICAGE_HOME=/home/demo \
97+
"${AICAGE_IMAGE_BASE_IMAGE}" \
98+
-c '
99+
set -euo pipefail
100+
mkdir -p /home/demo
101+
chown 0:0 /home/demo
102+
/usr/local/bin/entrypoint.sh -c "set -euo pipefail; echo \"\$(id -u):\$(id -g):\${HOME}:\$(stat -c %u:%g /home/demo)\""
103+
'
104+
[ "$status" -eq 0 ]
105+
result="$(printf '%s\n' "${output}" | tail -n 1)"
106+
IFS=':' read -r uid gid home home_uid home_gid <<<"${result}"
107+
[ "${uid}" -eq 2234 ]
108+
[ "${gid}" -eq 3234 ]
109+
[ "${home}" = "/home/demo" ]
110+
[ "${home_uid}" -eq 2234 ]
111+
[ "${home_gid}" -eq 3234 ]
112+
}

0 commit comments

Comments
 (0)