From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.3 required=5.0 tests=MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 23337 invoked from network); 22 Nov 2020 22:56:39 -0000 Received: from mother.openwall.net (195.42.179.200) by inbox.vuxu.org with ESMTPUTF8; 22 Nov 2020 22:56:39 -0000 Received: (qmail 9686 invoked by uid 550); 22 Nov 2020 22:56:34 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: musl@lists.openwall.com Received: (qmail 9650 invoked from network); 22 Nov 2020 22:56:33 -0000 Date: Sun, 22 Nov 2020 17:56:21 -0500 From: Rich Felker To: musl@lists.openwall.com Message-ID: <20201122225619.GR534@brightrain.aerifal.cx> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="IihjUyvzd0n5Ehsu" Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Subject: [musl] realpath without procfs -- should be ready for inclusion --IihjUyvzd0n5Ehsu Content-Type: text/plain; charset=us-ascii Content-Disposition: inline I put this aside for a while, but realpath without procfs was one of the things I wanted to get in this release cycle, and I had it essentially finished back in September. Here's the latest version. Compared to the earlier draft, it handles // more consistently (//. and //.. both resolve to //) and expands getcwd only as a final step if needed, rather than doing it first. In the case where an absolute symlink is reached via a relative path, this saves a syscall, avoids repeated absolute path traversal at each step, and avoids spuriously erroring out with ENAMETOOLONG when the working directory is deeper than PATH_MAX but the absolute link target has a valid absolute pathname. To recap, the motivation for the rewrite without procfs dependency was having things work before procfs is mounted or in chroot/namespace where it's intentionally unavailable. I originally considered keeping the procfs based version and only using the new one as a fallback, but I discovered there are cases (involving chroot, namespaces, etc.) where the answer from procfs is wrong and validating it requires basically the same procedure as implementing it manually (walking and performing readlink on each path component). Rich --IihjUyvzd0n5Ehsu Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="realpath8.c" #define _GNU_SOURCE #include #include #include #include #include static inline int at_dotdot(const char *end, size_t len) { if (len<2) return 0; if (len>2 && end[-3]!='/') return 0; return end[-1]=='.' && end[-2]=='.'; } char *realpath(const char *restrict filename, char *restrict resolved) { char stack[PATH_MAX]; char buf[resolved ? 1 : PATH_MAX]; char *output = resolved ? resolved : buf; size_t p, q, l, cnt=0; l = strnlen(filename, sizeof stack + 1); if (!l) { errno = ENOENT; return 0; } if (l >= sizeof stack) goto toolong; p = sizeof stack - l - 1; q = 0; memcpy(stack+p, filename, l+1); while (stack[p]) { int up = 0; if (stack[p] == '/') { q=0; output[q++] = '/'; p++; /* Initial // is special. */ if (stack[p] == '/' && stack[p+1] != '/') { output[q++] = '/'; } while (stack[p] == '/') p++; continue; } char *z = __strchrnul(stack+p, '/'); l = z-(stack+p); if (l==1 && stack[p]=='.') { p += l; while (stack[p] == '/') p++; continue; } if (at_dotdot(stack+p+l, l)) { if (q && !at_dotdot(output+q, q)) { while(q && output[q-1]!='/') q--; if (q>1 && (q>2 || output[0]!='/')) q--; p += l; while (stack[p] == '/') p++; continue; } up = 1; } if (q && output[q-1] != '/') { if (!p) goto toolong; stack[--p] = '/'; l++; } if (q+l >= PATH_MAX) goto toolong; memcpy(output+q, stack+p, l); output[q+l] = 0; p += l; if (up) goto notlink; ssize_t k = readlink(output, stack, p); if (k==-1) { if (errno == EINVAL) { notlink: q += l; while (stack[p] == '/') p++; continue; } return 0; } if (k==p) goto toolong; if (++cnt == SYMLOOP_MAX) { errno = ELOOP; return 0; } p -= k; memmove(stack+p, stack, k); } output[q] = 0; if (output[0] != '/') { if (!getcwd(stack, sizeof stack)) return 0; l = strlen(stack); /* Cancel any initial .. components. */ p = 0; while (q-p>=2 && at_dotdot(output+p+2, p+2)) { while(l>1 && stack[l-1]!='/') l--; if (l>1) l--; p += 2; if (p= PATH_MAX) goto toolong; memmove(output + l, output + p, q - p + 1); memcpy(output, stack, l); } return resolved ? resolved : strdup(output); toolong: errno = ENAMETOOLONG; return 0; } --IihjUyvzd0n5Ehsu--