return false;
}
-static int unit_file_resolve_symlink(
+int unit_file_resolve_symlink(
const char *root_dir,
char **search_path,
const char *dir,
int dirfd,
const char *filename,
+ bool resolve_destination_target,
char **ret_destination) {
- _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL;
+ _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL, *_dir = NULL, *_filename = NULL;
int r;
- assert(dir);
- assert(dirfd >= 0);
+ /* This can be called with either dir+dirfd valid and filename just a name,
+ * or !dir && dirfd==AT_FDCWD, and filename being a full path.
+ *
+ * If resolve_destination_target is true, an absolute path will be returned.
+ * If not, an absolute path is returned for linked unit files, and a relative
+ * path otherwise. */
+
assert(filename);
assert(ret_destination);
+ assert(dir || path_is_absolute(filename));
+ assert(dirfd >= 0 || dirfd == AT_FDCWD);
r = readlinkat_malloc(dirfd, filename, &target);
if (r < 0)
return log_warning_errno(r, "Failed to read symlink %s%s%s: %m",
dir, dir ? "/" : "", filename);
+ if (!dir) {
+ r = path_extract_directory(filename, &_dir);
+ if (r < 0)
+ return r;
+ dir = _dir;
+
+ r = path_extract_filename(filename, &_filename);
+ if (r < 0)
+ return r;
+ if (r == O_DIRECTORY)
+ return log_warning_errno(SYNTHETIC_ERRNO(EISDIR),
+ "Unexpected path to a directory \"%s\", refusing.", filename);
+ filename = _filename;
+ }
+
bool is_abs = path_is_absolute(target);
if (root_dir || !is_abs) {
char *target_abs = path_join(is_abs ? root_dir : dir, target);
return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m",
dir, filename, target);
+ assert(path_is_absolute(simplified));
+
/* Check if the symlink goes outside of our search path.
- * If yes, it's a linked unit file or mask, and we don't care about the target name.
- * Let's just store the link source directly.
- * If not, let's verify that it's a good symlink. */
+ * If yes, it's a linked unit file or mask, and we don't care about the target name
+ * when loading units, and we return the link *source* (resolve_destination_target == false);
+ * When this is called for installation purposes, we want the final destination,
+ * so we return the *target*.
+ *
+ * Otherwise, let's verify that it's a good alias.
+ */
const char *tail = path_startswith_strv(simplified, search_path);
if (!tail) {
log_debug("Linked unit file: %s/%s → %s", dir, filename, simplified);
- dst = path_join(dir, filename);
- if (!dst)
- return log_oom();
+ if (resolve_destination_target)
+ dst = TAKE_PTR(simplified);
+ else {
+ dst = path_join(dir, filename);
+ if (!dst)
+ return log_oom();
+ }
} else {
- r = path_extract_filename(simplified, &dst);
+ _cleanup_free_ char *target_name = NULL;
+
+ r = path_extract_filename(simplified, &target_name);
if (r < 0)
return r;
- bool self_alias = streq(dst, filename);
+ bool self_alias = streq(target_name, filename);
if (is_path(tail))
log_full(self_alias ? LOG_DEBUG : LOG_WARNING,
if (r < 0)
return r;
- if (self_alias)
- /* A self-alias that has no effect */
+ if (self_alias && !resolve_destination_target)
+ /* A self-alias that has no effect when loading, let's just ignore it. */
return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
"Unit file self-alias: %s/%s → %s, ignoring.",
- dir, filename, dst);
+ dir, filename, target_name);
+
+ log_debug("Unit file alias: %s/%s → %s", dir, filename, target_name);
- log_debug("Unit file alias: %s/%s → %s", dir, filename, dst);
+ dst = resolve_destination_target ? TAKE_PTR(simplified) : TAKE_PTR(target_name);
}
*ret_destination = TAKE_PTR(dst);
r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
*dir, dirfd(d), de->d_name,
+ /* resolve_destination_target= */ false,
&dst);
if (r == -ENOMEM)
return r;
const char *path,
const LookupPaths *lp,
SearchFlags flags) {
-
- _cleanup_free_ char *resolved = NULL;
int r;
r = unit_file_load(c, info, path, lp->root_dir, flags);
if (r != -ELOOP || (flags & SEARCH_DROPIN))
return r;
- r = chase_symlinks(path, lp->root_dir, CHASE_WARN | CHASE_NONEXISTENT, &resolved, NULL);
- if (r >= 0 &&
- lp->root_dir &&
- path_equal_ptr(path_startswith(resolved, lp->root_dir), "dev/null"))
- /* When looking under root_dir, we can't expect /dev/ to be mounted,
- * so let's see if the path is a (possibly dangling) symlink to /dev/null. */
- info->type = UNIT_FILE_TYPE_MASKED;
-
- else if (r > 0 && null_or_empty_path(resolved) > 0)
+ /* This is a symlink, let's read and verify it. */
+ r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
+ NULL, AT_FDCWD, path,
+ true, &info->symlink_target);
+ if (r < 0)
+ return r;
+ r = null_or_empty_path_with_root(info->symlink_target, lp->root_dir);
+ if (r < 0 && r != -ENOENT)
+ return log_debug_errno(r, "Failed to stat %s: %m", info->symlink_target);
+ if (r > 0)
info->type = UNIT_FILE_TYPE_MASKED;
-
- else {
- _cleanup_free_ char *target = NULL;
- const char *bn;
- UnitType a, b;
-
- /* This is a symlink, let's read it. We read the link again, because last time
- * we followed the link until resolution, and here we need to do one step. */
-
- r = readlink_malloc(path, &target);
- if (r < 0)
- return r;
-
- bn = basename(target);
-
- if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) {
-
- if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN))
- return -EINVAL;
-
- } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
-
- if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
- return -EINVAL;
-
- } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) {
-
- if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE))
- return -EINVAL;
- } else
- return -EINVAL;
-
- /* Enforce that the symlink destination does not
- * change the unit file type. */
-
- a = unit_name_to_type(info->name);
- b = unit_name_to_type(bn);
- if (a < 0 || b < 0 || a != b)
- return -EINVAL;
-
- if (path_is_absolute(target))
- /* This is an absolute path, prefix the root so that we always deal with fully qualified paths */
- info->symlink_target = path_join(lp->root_dir, target);
- else
- /* This is a relative path, take it relative to the dir the symlink is located in. */
- info->symlink_target = file_in_same_dir(path, target);
- if (!info->symlink_target)
- return -ENOMEM;
-
+ else
info->type = UNIT_FILE_TYPE_SYMLINK;
- }
return 0;
}