X-Authentication-Warning: delorie.com: mail set sender to djgpp-workers-bounces using -f X-Recipient: djgpp-workers AT delorie DOT com X-Authenticated: #27081556 X-Provags-ID: V01U2FsdGVkX18brdpvYi0bpQNX99XPYSU3aqF4fvSsKGD6Isim+Z njpqm9RqCRwauY From: Juan Manuel Guerrero To: djgpp-workers AT delorie DOT com Subject: fopen/open and pathnames with trailing slash Date: Sun, 9 Aug 2009 15:51:18 +0200 User-Agent: KMail/1.9.10 MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline Message-Id: <200908091551.19052.juan.guerrero@gmx.de> X-Y-GMX-Trusted: 0 X-FuHaFi: 0.42 Reply-To: djgpp-workers AT delorie DOT com While I was trying to port m4 I have noticed that the configure script complains that both functions fopen() and open() do not handle correctly the case when the pathnames end with a slash. 1) According to POSIX: If the filename contains at least one non-slash character and ends with one or more trailing slashes and one of the modes O_CREAT, O_WRONLY or O_RDWR is specified, then open() must fail with EISDIR. The same applies for fopen() if the modes w or a have been specified. 2) According to POSIX: If the filename ends in a slash and the file descriptor of the named file without the slash does not refer to a directory, then open() must fail with ENOTDIR. See: , and Because open() is also called by fopen() to do the job, the issue needs only to be fixed there. Because I need to operate with the filename I had to check that the string is not NULL or empty. In that case fopen()/open() terminates with EINVAL. I do not know if this is correct, but I need to handle this case in some way. As usual, suggestions, objections, comments are welcome. Regards, Juan M. Guerrero 2009-07-17 Juan Manuel Guerrero Diffs against djgpp CVS head of 2009-04-14. * src/libc/posix/fcntl/open.c: Implementation of POSIX conforming handling of pathnames ending with a slash. * tests/libc/posix/fcntl/open.c: Checks added to test implementation of POSIX conforming handling of pathnames ending with a slash. * src/docs/kb/wc204.txi: Info about open() added. diff -aprNU5 djgpp.orig/src/docs/kb/wc204.txi djgpp/src/docs/kb/wc204.txi --- djgpp.orig/src/docs/kb/wc204.txi 2009-04-13 12:34:56 +0000 +++ djgpp/src/docs/kb/wc204.txi 2009-07-17 20:41:30 +0000 @@ -1181,5 +1181,13 @@ Those functions that require the trailin @findex mkstemp AT r{, and SUS compliance} The function prototypes of @code{mktemp} and @code{mkstemp} are now also in @code{}. This is to achieve Single Unix Specification compliance. To keep backward compatibility, the prototypes are also kept in @code{} but their usage is deprecated. + +@findex fopen AT r{, and SUS compliance} +@findex open AT r{, and SUS compliance} +To achieve @acronym{Single Unix Specification} compliance, the functions @code{fopen} +and @code{open} will fail with EISDIR if the pathname ends with a slash and one of +@code{O_CREAT}, @code{O_WRONLY} or @code{O_RDWR} is specified. They will also fail +with ENOTDIR if @code{O_RDONLY} is specified and the named file ends with a slash +but it is not a directory. diff -aprNU5 djgpp.orig/src/libc/posix/fcntl/open.c djgpp/src/libc/posix/fcntl/open.c --- djgpp.orig/src/libc/posix/fcntl/open.c 2003-11-21 22:54:56 +0000 +++ djgpp/src/libc/posix/fcntl/open.c 2009-07-17 20:55:46 +0000 @@ -1,5 +1,6 @@ +/* Copyright (C) 2009 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ @@ -10,10 +11,11 @@ #include #include #include #include #include +#include #include #include #include #include #include @@ -21,10 +23,16 @@ #include #include #include +#define IS_DRIVE_SPECIFIER(path) ((path)[0] && ((path)[1] == ':')) +#define IS_ROOT_DIR(path) ((IS_DRIVE_SPECIFIER(path) && IS_SLASH(path[2]) && ((path)[3] == '\0')) || \ + (IS_SLASH(path[0]) && ((path)[1] == '\0'))) +#define IS_SLASH(path) ((path) == '/' || (path) == '\\') + + /* Extra share flags that can be indicated by the user */ int __djgpp_share_flags; /* Move a file descriptor FD such that it is at least MIN_FD. If the file descriptor is changed (meaning it was origially @@ -108,14 +116,35 @@ opendir_as_fd (const char *filename, con int open(const char* filename, int oflag, ...) { const int original_oflag = oflag; - int fd, dmode, bintext, dont_have_share; + int fd, dmode, bintext; char real_name[FILENAME_MAX + 1]; - int should_create = (oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); - int dirs_solved = 0; /* Only directories resolved in real_name? */ + bool should_create = (oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); + bool dirs_solved = false; /* Only directories resolved in real_name? */ + bool dont_have_share, is_root_dir; + size_t length; + + + if (!(length = strlen(filename))) + { + errno = EINVAL; + return -1; + } + else + is_root_dir = IS_ROOT_DIR(filename); + + /* According to POSIX: If the filename contains at least one + non-slash character and ends with one or more trailing slashes + and one of O_CREAT, O_WRONLY, O_RDWR is specified, then fail. */ + if (((oflag & (O_RDONLY | O_CREAT | O_WRONLY | O_RDWR)) != O_RDONLY) && \ + !is_root_dir && IS_SLASH(filename[length - 1])) + { + errno = EISDIR; + return -1; + } /* Solve symlinks and honor O_NOLINK flag */ if (oflag & O_NOLINK) { if (!__solve_dir_symlinks(filename, real_name)) @@ -256,10 +285,19 @@ open(const char* filename, int oflag, .. /* Don't call _creat on existing files for which _open fails, since the file could be truncated as a result. */ else if ((oflag & O_CREAT)) fd = _creat(real_name, dmode); } + else + /* According to POSIX: If the named file without the slash + is not a directory, open() must fail with ENOTDIR. */ + if (!is_root_dir && IS_SLASH(filename[length - 1]) && access(real_name, D_OK)) + { + close(fd); + errno = ENOTDIR; + return -1; + } } /* Is the target a directory? If so, generate a file descriptor * for the directory. Skip the rest of `open', because it does not * apply to directories. */ diff -aprNU5 djgpp.orig/tests/libc/posix/fcntl/open.c djgpp/tests/libc/posix/fcntl/open.c --- djgpp.orig/tests/libc/posix/fcntl/open.c 2003-11-21 22:54:56 +0000 +++ djgpp/tests/libc/posix/fcntl/open.c 2009-07-17 20:28:18 +0000 @@ -115,10 +115,58 @@ int main(void) testnum, strerror(errno)); exit(EXIT_FAILURE); } printf("Test %d passed\n", testnum); + ++testnum; + fd = open("test3/", O_CREAT | O_WRONLY | O_RDWR); + if (fd != -1) + { + fprintf(stderr, "Test %d failed - unexpected open() success.\n", + testnum); + exit(EXIT_FAILURE); + } + if (errno != EISDIR) + { + fprintf(stderr, "Test %d failed - wrong errno returned: %s\n", + testnum, strerror(errno)); + exit(EXIT_FAILURE); + } + printf("Test %d passed\n", testnum); + + ++testnum; + fd = open("test3/", O_RDONLY ); + if (fd != -1) + { + fprintf(stderr, "Test %d failed - unexpected open() success.\n", + testnum); + exit(EXIT_FAILURE); + } + if (errno != ENOTDIR) + { + fprintf(stderr, "Test %d failed - wrong errno returned: %s\n", + testnum, strerror(errno)); + exit(EXIT_FAILURE); + } + printf("Test %d passed\n", testnum); + + ++testnum; + fd = open("", O_CREAT | O_WRONLY | O_RDWR | O_EXCL | O_NOLINK); + if (fd != -1) + { + fprintf(stderr, "Test %d failed - unexpected open() success.\n", + testnum); + exit(EXIT_FAILURE); + } + if (errno != EINVAL) + { + fprintf(stderr, "Test %d failed - wrong errno returned: %s\n", + testnum, strerror(errno)); + exit(EXIT_FAILURE); + } + printf("Test %d passed\n", testnum); + test_success("test2/test1", O_RDONLY | O_NOLINK, "!"); test_success("dir/test2", O_RDONLY, "tstlink2"); test_o_temporary(); puts("PASS"); return EXIT_SUCCESS;