Bug 205871 - install(1) in endless loop for orphaned symlinks
Summary: install(1) in endless loop for orphaned symlinks
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: bin (show other bugs)
Version: CURRENT
Hardware: Any Any
: --- Affects Only Me
Assignee: Mateusz Guzik
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-01-04 12:28 UTC by Frank Behrens
Modified: 2019-05-04 22:45 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Frank Behrens 2016-01-04 12:28:25 UTC
install(1) with option "-d" creates directories. If the final target or an intermediate directory is a symbolic link, that points to a none existing file/directory, then install will enter an endless loop.

I believe the behaviour was introduced with
base r272026 | mjg | 2014-09-23 13:41:09 +0200 (Tue, 23 Sep 2014).

It was observed when using mergemaster for a jail setup with ezjail. In that case symlinks visited from host can point to not existing directories on host system.

environment: 
FreeBSD 11.0-CURRENT #10 r292982M:

how to repeat:
% ln -s /notexists /tmp/
% install -d /tmp/notexists &
[1] 9843
% ps -v -p 9843
 PID STAT    TIME  SL RE PAGEIN  VSZ  RSS LIM TSIZ  %CPU %MEM COMMAND
9843 R    1:12.80 127 73      0 8356 2024   -   28 100.0  0.0 install -d /tmp/notexists
% kill -ABRT 9843
% gdb /usr/bin/install install.core
(gdb) bt
#0  stat () at stat.S:3
#1  0x0000000000402e0b in main (argc=<value optimized out>, argv=<value optimized out>) at /usr/src11/usr.bin/xinstall/xinstall.c:1269
(gdb) list 1269
1264            for (p = path;; ++p)
1265                    if (!*p || (p != path && *p  == '/')) {
1266                            ch = *p;
1267                            *p = '\0';
1268    again:
1269                            if (stat(path, &sb) < 0) {
1270                                    if (errno != ENOENT)
1271                                            err(EX_OSERR, "stat %s", path);
1272                                    if (mkdir(path, 0755) < 0) {
1273                                            if (errno == EEXIST)
(gdb) q

The stat call returns ENOENT and mkdir returns with EEXIST - that creates an endless loop.
Comment 1 Mateusz Guzik freebsd_committer freebsd_triage 2016-01-06 05:22:31 UTC
Bugreport looks legitimate. However, it is unclear what to do here - do an lstat or stat + lstat combo? I'll have to look around.
Comment 2 Jilles Tjoelker freebsd_committer freebsd_triage 2016-01-06 21:04:54 UTC
(In reply to Mateusz Guzik from comment #1)
I think a stat() call should be done after mkdir() fails with [EEXIST]; however, after that stat() call, we should not retry mkdir(). The stat() call may fail (error), report non-directory (error) or report directory (continue silently).

Replacing the existing stat() call with lstat() will fail for symlinks to directories anywhere in the path.

Another direction is to pass pathnames ending with a slash to stat() (or lstat()) and mkdir(). Per POSIX, this will cause lstat() to follow the symlink (and fail if the file pointed to is not a directory) and mkdir() to create the directory through the symlink. However, Linux does not implement the latter.
Comment 3 Eitan Adler freebsd_committer freebsd_triage 2018-05-28 19:49:34 UTC
batch change:

For bugs that match the following
-  Status Is In progress 
AND
- Untouched since 2018-01-01.
AND
- Affects Base System OR Documentation

DO:

Reset to open status.


Note:
I did a quick pass but if you are getting this email it might be worthwhile to double check to see if this bug ought to be closed.
Comment 4 Mateusz Guzik freebsd_committer freebsd_triage 2019-05-04 22:45:40 UTC
I forgot about this bug. The issue was fixed in r324547 ( https://svnweb.freebsd.org/base?view=revision&revision=324547 ).