Вопрос или проблема
Здесь новый пользователь Linux, только что перешедший с Windows 10 на Fedora 41. Много опыта с различными операционными системами, включая разные файловые системы *nix и Unix-подобные системы.
У меня есть стандартная практика использования символических ссылок из тестовых папок в репозитории кода для воссоздания локальной структуры развернутой производственной папки для тестирования, при этом отделяя тестирование и репозиторий кода. У меня много проектов настроено таким образом для локального тестирования.
Это выглядит следующим образом, например:
~/dev
test
a-project
app => ../../code/a-project/src
(cfg)
(web)
dta
cfg
code
a-project
src
cfg
web
Ключевой момент этой проблемы в том, что папка src/cfg
(которая в производстве является папкой app/cfg
) содержит файлы, которые имеют относительные ссылки на необязательные файлы в dta/cfg
, которые загружаются, если они существуют в производстве, и ожидается, что они будут относительными к папке app/cfg
. Таким образом, ../../dta/cfg/Optional.json
при развертывании ссылается на папку dta
. PWD (текущий рабочий каталог) — это корень приложения (в тестировании это ~/dev/test/a-project
), и все пути в ПО указаны относительно PWD. Эти пути обрабатываются в Java-коде с использованием класса File
Java, так что это не проблема поведения оболочки.
Это работает прекрасно в Windows, поскольку файловая система принимает путь app/cfg/../../dta/cfg/Optional.json
буквально и сначала разрешает относительные ссылки, результатом чего является dta/cfg/Optional.json
(ведущий app/cfg
исключается следующими ../..
.
Но в Linux, по-видимому, сначала разрешается символическая ссылка (app/cfg/
=> dev/code/project/src/)
и только затем применяются относительные сегменты пути ../../dta/cfg/Optional.json
, что приводит к отсутствию файла, потому что, конечно, файл не находится в ~/dev/code/project/dta/cfg
.
Независимо от аргументов о том, правильно ли это технически или нет, есть ли какой-либо способ, которым я могу сообщить Linux на любом уровне, чтобы разрешить относительные сегменты пути перед разрешением символических ссылок, чтобы это неожиданное и непредсказуемое поведение не происходило? Помимо того, что это сбивает с толку и не очевидно для меня, у меня много существующего кода, который ожидает, что пути сначала разрешат относительные ссылки, используя очевидный путь, а не основной путь.
Файловая система для моей разработки — это ext4 с включенной сверткой регистра (пришедший с Windows и необходимостью оставаться совместимым с другими разработчиками, использующими Mac и Windows), и я бы очень, очень не хотел проводить дестабилизирующие, инвазивные изменения в кучу классов Java для реализации разрешения относительных путей перед передачей пути ОС; как потому, что это потенциально хрупко, ведет к уязвимостям безопасности, так и потому, что это нужно только для моего тестирования, поскольку производственные системы (серверы Ubuntu Linux) не нуждаются в этих символических ссылках.
Простой ответ – “нет”. Без изменения Java-кода, символические ссылки не будут делать то, что вы хотите.
Почему?
Это потому, что ..
не то, что вы думаете…
Поведение не является непредсказуемым. Оно лишь кажется удивительным, потому что команда cd
делает что-то странное. Во всех других контекстах ..
интерпретируется ядром как реальный элемент в файловой системе. Каждая директория фактически содержит дочерний элемент с именем ..
, который напрямую ссылается на своего родителя.
Таким образом, foo/..
говорит ядру следовать символической ссылке foo
, а затем искать имя ..
в этой директории. Ядро на самом деле не делает ничего отличного от того, что оно делает для пути foo/bar
.
Опции
Альтернативный макет конфигурации
Как наблюдение: проблема, с которой вы сталкиваетесь, по-видимому, возникает из-за того, что ваша конфигурация также хранится в вашем каталоге исходного кода, что является необычным шаблоном.
Обычно вы бы поместили конфигурацию в тестовую директорию, сделав ее специфичной для теста. Конфигурация может ссылаться на местоположение приложения или ваш скрипт запуска знает макет.
Вы всегда можете сделать символические ссылки на отдельные конфигурационные файлы в каталоге test/app/cfg
.
Используйте монтирование привязки / docker
Я лично предпочитаю использовать монтирование привязки для таких вещей и для этой цели обычно использую Docker и docker compose как способ настройки теста.
Вы можете предпочесть использовать оболочный скрипт для настройки монтирования привязки для вас, так как Java обычно требует какого-то запуска скрипта.
Например: файл docker-compose.yaml в тестовой директории
services:
test:
image: openjdk:25-jdk-bullseye
volumes:
- ../../code/a-project/src:/app/
- ./data:/data/
# Каков бы ни был ваш скрипт запуска...
run: run.sh
Также возможно установить это вручную, используя unshare
и mount --bind
, и, наконец, exec
в скрипте. Но это более сложный вариант.
Ответ или решение
Вопрос о том, может ли файловая система Linux сначала разрешить относительный путь перед разрешением символической ссылки, на самом деле раскрывает несколько фундаментальных аспектов работы файловых систем Unix-подобных систем. Давайте разберемся подробнее, почему Linux ведет себя именно так и какие решения могут быть предложены.
Теория
В файловых системах Unix-подобных систем, включая Linux, символические ссылки (symlinks) работают как особые файловые типы, которые указывают на другой файл или каталог. Когда операционная система сталкивается с символической ссылкой, она следит этому указателю для получения объекта, на который ссылается symlink. Проблема, с которой вы столкнулись, заключается в том, что Linux изначально разрешает символические ссылки перед обработкой относительных путей. Это поведение определено проектом и реализацией ядра, в отличие от Windows, где относительные ссылки разрешаются без учета символической ссылки.
Пример
Ваша структура каталогов выглядит следующим образом:
~/dev
test
a-project
app => ../../code/a-project/src
(cfg)
(web)
dta
cfg
code
a-project
src
cfg
web
В Windows путь app/cfg/../../dta/cfg/Optional.json
интерпретируется как dta/cfg/Optional.json
. Однако в Linux из-за поведения разрешения symlink сначала путь интерпретируется как ~/dev/code/project/dta/cfg
, что вызывает ошибку «файл не найден».
Применение
К сожалению, в рамках стандартных возможностей ext4 и других файловых систем Linux нет способа изменить это поведение без внесения изменений в само приложение или его окружение. Однако есть некоторые подходы, которые могут помочь обойти эту проблему:
-
Изменение структуры конфигурации: Рассмотрите возможность держать конфигурационные файлы отдельно от исходного кода. Это позволит избежать необходимости в относительных путях, зависящих от символических ссылок.
-
Символические ссылки на уровне файлов: Вы можете создавать символические ссылки на отдельные конфигурационные файлы вместо папок. Это позволит более точно контролировать, какие файлы необходимы для тестирования.
-
Использование привязок монтирования: Используйте bind mounts, чтобы заставить файловую систему отображать требуемую структуру каталогов. Вы можете настроить это вручную через команды
mount --bind
. -
Docker и Docker Compose: Настройка окружения разработки с помощью контейнеров. Используя
docker-compose.yaml
, вы можете настроить контейнер, который будет отображать необходимую структуру файлов для тестирования. -
Скрипты автоматизации: Разработайте скрипты для подготовки среды разработки, которые будут устанавливать симлинки/запуски проектов в нужной конфигурации.
Несмотря на то что текущий подход Windows, возможно, кажется более интуитивным, понимание работы Unix-подобных файловых систем позволит вам строить более надежные и универсальные решения для разработки и тестирования.