From owner-svn-src-head@freebsd.org Tue May 31 09:00:42 2016 Return-Path: Delivered-To: svn-src-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id A324AB55514; Tue, 31 May 2016 09:00:42 +0000 (UTC) (envelope-from torek@torek.net) Received: from elf.torek.net (mail.torek.net [96.90.199.121]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 6A142133D; Tue, 31 May 2016 09:00:41 +0000 (UTC) (envelope-from torek@torek.net) Received: from elf.torek.net (localhost [127.0.0.1]) by elf.torek.net (8.14.9/8.14.9) with ESMTP id u4V8VBg1081055; Tue, 31 May 2016 01:31:11 -0700 (PDT) (envelope-from torek@torek.net) Message-Id: <201605310831.u4V8VBg1081055@elf.torek.net> From: Chris Torek To: Ed Schouten cc: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: Re: svn commit: r301024 - head/sbin/swapon In-reply-to: Your message of "Tue, 31 May 2016 06:45:19 -0000." <201605310645.u4V6jJdF078504@repo.freebsd.org> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <81053.1464683471.1@elf.torek.net> Date: Tue, 31 May 2016 01:31:11 -0700 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (elf.torek.net [127.0.0.1]); Tue, 31 May 2016 01:31:11 -0700 (PDT) X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.22 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 31 May 2016 09:00:42 -0000 > This change makes the code use the POSIX basename() function. It has the > advantage that (if implemented correctly), it also imposes no restrict > on the pathname length. I'm not sure what the parenthetic remark "if implemented correctly" means, but libc basename() has this in it: char * basename(const char *path) { static char *bname = NULL; if (bname == NULL) { bname = (char *)malloc(MAXPATHLEN); if (bname == NULL) return (NULL); } return (basename_r(path, bname)); } which means that it is not only not thread-safe, it also imposes restrictions on pathname length. I recently wrote a pair of can-be-thread-safe yet can-be- unlimited-path-length yet can-be-backwards-compatible basename and dirname functions (I tested to see if they produced the same errno's and oddball outputs like for dirname("///")). I'll toss them in here in case anyone has some enthusiasm for fixing the mess. Of course they are equally non-standard, unless we can convince a future POSIX committee or something. (I also wrote some halfway useable reentrant getpw* and getgr* functions but they need more thought and do not play well with nsswitch so I am not including them here. NB: rfuncs.h just contains declarations for these reentrant r_* functions.) Chris ------- /* * Copyright 2016 Chris Torek * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include "rfuncs.h" /* * This is essentially a clone of the BSD basename_r function, * which is like POSIX basename() but puts the result in a user * supplied buffer. * * In BSD basename_r, the buffer must be least MAXPATHLEN bytes * long. In our case we take the size of the buffer as an argument. * * Note that it's impossible in general to do this without * a temporary buffer since basename("foo/bar") is "bar", * but basename("foo/bar/") is still "bar" -- no trailing * slash is allowed. * * The return value is your supplied buffer , or NULL if * the length of the basename of the supplied equals or * exceeds your indicated . * * As a special but useful case, if you supply NULL for the * argument, we allocate the buffer dynamically to match the * basename, i.e., the result is basically strdup()ed for you. * In this case is ignored (recommended: pass 0 here). */ char * r_basename(const char *path, char *buf, size_t bufsize) { const char *endp, *comp; size_t len; /* * NULL or empty path means ".". This is perhaps overly * forgiving but matches libc basename_r(), and avoids * breaking the code below. */ if (path == NULL || *path == '\0') { comp = "."; len = 1; } else { /* * Back up over any trailing slashes. If we reach * the top of the path and it's still a trailing * slash, it's also a leading slash and the entire * path is just "/" (or "//", or "///", etc). */ endp = path + strlen(path) - 1; while (*endp == '/' && endp > path) endp--; /* Invariant: *endp != '/' || endp == path */ if (*endp == '/') { /* then endp==path and hence entire path is "/" */ comp = "/"; len = 1; } else { /* * We handled empty strings earlier, and * we just proved *endp != '/'. Hence * we have a non-empty basename, ending * at endp. * * Back up one path name component. The * part between these two is the basename. * * Note that we only stop backing up when * either comp==path, or comp[-1] is '/'. * * Suppose path[0] is '/'. Then, since *endp * is *not* '/', we had comp>path initially, and * stopped backing up because we found a '/' * (perhaps path[0], perhaps a later '/'). * * Or, suppose path[0] is NOT '/'. Then, * either there are no '/'s at all and * comp==path, or comp[-1] is '/'. * * In all cases, we want all bytes from *comp * to *endp, inclusive. */ comp = endp; while (comp > path && comp[-1] != '/') comp--; len = (size_t)(endp - comp + 1); } } if (buf == NULL) { buf = malloc(len + 1); if (buf == NULL) return (NULL); } else { if (len >= bufsize) { errno = ENAMETOOLONG; return (NULL); } } memcpy(buf, comp, len); buf[len] = '\0'; return (buf); } /* * This is much like POSIX dirname(), but is reentrant. * * We examine a path, find the directory portion, and copy that * to a user supplied buffer of the given size . * * Note that dirname("/foo/bar/") is "/foo", dirname("/foo") is "/", * and dirname("////") is "/". However, dirname("////foo/bar") is * "////foo" (we do not resolve these leading slashes away -- this * matches the BSD libc behavior). * * The return value is your supplied buffer , or NULL if * the length of the dirname of the supplied equals or * exceeds your indicated . * * As a special but useful case, if you supply NULL for the * argument, we allocate the buffer dynamically to match the * dirname, i.e., the result is basically strdup()ed for you. * In this case is ignored (recommended: pass 0 here). */ char * r_dirname(const char *path, char *buf, size_t bufsize) { const char *endp, *dirpart; size_t len; /* * NULL or empty path means ".". This is perhaps overly * forgiving but matches libc dirname(), and avoids breaking * the code below. */ if (path == NULL || *path == '\0') { dirpart = "."; len = 1; } else { /* * Back up over any trailing slashes, then back up * one path name, then back up over more slashes. * In all cases, stop as soon as endp==path so * that we do not back out of the buffer entirely. * * The first loop takes care of trailing slashes * in names like "/foo/bar//" (where the dirname * part is to be "/foo"), the second strips out * the non-dir-name part, and the third leaves us * pointing to the end of the directory component. * * If the entire name is of the form "/foo" or * "//foo" (or "/foo/", etc, but we already * handled trailing slashes), we end up pointing * to the leading "/", which is what we want; but * if it is of the form "foo" (or "foo/", etc) we * point to a non-slash. So, if (and only if) * endp==path AND *endp is not '/', the dirname is * ".", but in all cases, the LENGTH of the * dirname is (endp-path+1). */ endp = path + strlen(path) - 1; while (endp > path && *endp == '/') endp--; while (endp > path && *endp != '/') endp--; while (endp > path && *endp == '/') endp--; len = (size_t)(endp - path + 1); if (endp == path && *endp != '/') dirpart = "."; else dirpart = path; } if (buf == NULL) { buf = malloc(len + 1); if (buf == NULL) return (NULL); } else { if (len >= bufsize) { errno = ENAMETOOLONG; return (NULL); } } memcpy(buf, dirpart, len); buf[len] = '\0'; return (buf); }