Message-ID: <3993F0E7.9A25BD50@softhome.net> Date: Fri, 11 Aug 2000 14:26:15 +0200 From: Laurynas Biveinis X-Mailer: Mozilla 4.74 [en] (Win98; U) X-Accept-Language: lt,en MIME-Version: 1.0 To: DJGPP Workers Subject: Patch: __solve_symlinks() Content-Type: text/plain; charset=iso-8859-4 Content-Transfer-Encoding: 7bit Reply-To: djgpp-workers AT delorie DOT com This new function is the core of symlink support - it will be called by most file handling functions to resolve symlinks before calling DOS. If it breaks, everything else breaks too. So please report any bugs there ASAP. The first one who shows me a testcase where __solve_symlinks does not correctly work, will receive my thanks via snail-mail postcard ;-) Laurynas ? djgpp/djgpp.diff ? djgpp/src/libc/posix/sys/stat.diff Index: djgpp/include/libc/symlink.h =================================================================== RCS file: symlink.h diff -N symlink.h --- /dev/null Tue May 5 16:32:27 1998 +++ symlink.h Fri Aug 11 08:14:51 2000 @@ -0,0 +1,39 @@ +/* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ + +/* Written by Laurynas Biveinis */ +/* This file contains some internal info related to symlinks */ +/* Note: symlink file format is still in internal include file */ +/* because I don't think it's required for user apps */ +#ifndef __dj_include_libc_symlink_h_ +#define __dj_include_libc_symlink_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __dj_ENFORCE_ANSI_FREESTANDING + +#ifndef __STRICT_ANSI__ + +#ifndef _POSIX_SOURCE + +/* A prototype for internal library function for fully resolving symlink */ +/* chain. Standard function readlink() solves only one symlink level. */ +/* If path name passed appears to be not a symlink, it is copied to result */ +/* string. Return code 1 means success, 0 - failure (to many links - errno */ +/* is set too). */ + +int __solve_symlinks(const char * __symlink_path, char * __real_path); + +#endif /* !_POSIX_SOURCE */ +#endif /* !__STRICT_ANSI__ */ +#endif /* !__dj_ENFORCE_ANSI_FREESTANDING */ + +#ifndef __dj_ENFORCE_FUNCTION_CALLS +#endif /* !__dj_ENFORCE_FUNCTION_CALLS */ + +#ifdef __cplusplus +} +#endif + +#endif /* !__dj_include_libc__h_ */ Index: djgpp/src/libc/compat/unistd/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/unistd/makefile,v retrieving revision 1.5 diff -u -r1.5 makefile --- makefile 2000/08/11 11:16:23 1.5 +++ makefile 2000/08/11 12:14:56 @@ -18,5 +18,6 @@ SRC += truncate.c SRC += usleep.c SRC += vfork.c +SRC += xsymlink.c include $(TOP)/../makefile.inc Index: djgpp/src/libc/compat/unistd/xsymlink.c =================================================================== RCS file: xsymlink.c diff -N xsymlink.c --- /dev/null Tue May 5 16:32:27 1998 +++ xsymlink.c Fri Aug 11 08:14:56 2000 @@ -0,0 +1,156 @@ +/* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ + +/* Written by Laurynas Biveinis */ +/* Internal source file specifying DJGPP symlink prefix and internal */ +/* function which fully resolves given symlink. (Function readlink() */ +/* resolves only last filename component and one symlink level.) */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xsymlink.h" + +int __solve_symlinks(const char * __symlink_path, char * __real_path) +{ + int bytes_copied; + char * start; + char * end; + int old_errno; + char fn_buf[FILENAME_MAX + 1]; + char resolved[FILENAME_MAX + 1]; + int link_level = 0; + int found_absolute_path; + int done_something; + char * ptr; + int non_trivial; + + if (!__symlink_path || !__real_path) + { + errno = EINVAL; + return 0; + } + + if (strlen(__symlink_path) > FILENAME_MAX) + { + errno = ENAMETOOLONG; + return 0; + } + + strcpy(__real_path, __symlink_path); + start = __real_path; + end = strpbrk(__real_path, "/\\"); + if (!end) + end = __real_path + strlen(__real_path); + while (*start) + { + /* Extract path component we will be resolving */ + strcpy(resolved, __real_path); + if (*end) + resolved[end - __real_path] = '\0'; + old_errno = errno; + found_absolute_path = 0; + done_something = 0; + non_trivial = 0; + /* Resolve that component. Repeat until we encounter non-symlink, + or not trivial symlink (in form 'dir/file'). */ + do + { + bytes_copied = readlink(resolved, fn_buf, FILENAME_MAX); + if (bytes_copied != -1) + { + done_something = 1; + link_level++; + fn_buf[bytes_copied] = '\0'; + strcpy(resolved, fn_buf); + /* FIXME: does absolute path check below work with chroot()? */ + if (((bytes_copied > 2) && (resolved[1] == ':')) || + ((bytes_copied > 0) && ((resolved[0] == '/') || + (resolved[0] == '\\')))) + { + found_absolute_path = 1; + } + /* If we found dir/file, do not iterate */ + else + { + if ((resolved[0] == '.') && + ((resolved[1] == '/') || (resolved[1] == '\\'))) + ptr = resolved + 2; + else + ptr = resolved; + while (ptr < resolved + strlen(resolved)) /* Skip last char */ + if ((*ptr == '/') || (*ptr == '\\')) + { + bytes_copied = -1; + non_trivial = 1; + break; + } + else + ptr++; + } + } + } while ((bytes_copied != -1) && (link_level <= _POSIX_LINK_MAX)); + if (link_level > _POSIX_LINK_MAX) + { + errno = ELOOP; + return 0; + } + else + errno = old_errno; + if (done_something) + { + /* If it wasn't the last path component resolved, save the + * unresolved tail for future + */ + if (*end) + strcpy(fn_buf, end); + else + fn_buf[0] = '\0'; + if (found_absolute_path) + { + /* Discard already resolved part, because symlink's target */ + /* is absolute path */ + strcpy(__real_path, resolved); + } + else + { + /* Add resolved symlink component to already resolved part */ + memcpy(start, resolved, strlen(resolved) + 1); + } + /* Append saved tail for further processing */ + if (fn_buf[0]) + strcat(__real_path, fn_buf); + } + if (done_something) + { + if (found_absolute_path) + { + /* Restart processing. God knows what's in the new absolute path */ + start = __real_path; + end = strpbrk(__real_path, "/\\"); + } + else if (non_trivial) + { + /* If we got component like 'dir/file', start resolving from */ + /* dir. */ + end = strpbrk(start + 1, "/\\"); + } + } + else + { + /* Resolve next component */ + start = end; + if ((*start == '/') || (*start == '\\')) + ++start; + end = strpbrk(end + 1, "/\\"); + } + if (!end) + end = __real_path + strlen(__real_path); + } + return 1; +} + Index: djgpp/src/libc/compat/unistd/xsymlink.txh =================================================================== RCS file: xsymlink.txh diff -N xsymlink.txh --- /dev/null Tue May 5 16:32:27 1998 +++ xsymlink.txh Fri Aug 11 08:14:56 2000 @@ -0,0 +1,37 @@ +@node __solve_symlinks, io +@subheading Syntax + +@example +#include + +int __solve_symlinks(const char *symlink_path, char *real_path); +@end example + +@subheading Description +This function fully resolves given symlink in @var{symlink_path} --- +all path components and all symlink levels are resolved. The +returned path in @var{real_path} is guaranteed to be symlink-clean +and understandable by DOS. If @var{symlink_path} does not contain +symlinks at all, it is simply copied to @var{real_path}. +@subheading Return Value + +Zero in case of error (and @code{errno} set to the appropriate +error code), non-zero in case of success. + +@subheading Portability + +@portability !ansi, !posix + +@subheading Example + +@example + + #include + #include + + __solve_symlinks(fn, file_name); + printf("File %s is really %s\n", fn, file_name); + + +@end example + Index: djgpp/tests/libc/compat/unistd/fail1 =================================================================== RCS file: fail1 diff -N fail1 --- /dev/null Tue May 5 16:32:27 1998 +++ fail1 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!fail2 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/fail2 =================================================================== RCS file: fail2 diff -N fail2 --- /dev/null Tue May 5 16:32:27 1998 +++ fail2 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!fail1 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/fail3 =================================================================== RCS file: fail3 diff -N fail3 --- /dev/null Tue May 5 16:32:27 1998 +++ fail3 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!dir1/fail1 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/compat/unistd/makefile,v retrieving revision 1.1 diff -u -r1.1 makefile --- makefile 2000/08/11 11:16:25 1.1 +++ makefile 2000/08/11 12:15:11 @@ -1,5 +1,6 @@ TOP=../.. SRC += readlink.c +SRC += xsymlink.c include $(TOP)/../makefile.inc Index: djgpp/tests/libc/compat/unistd/test4 =================================================================== RCS file: test4 diff -N test4 --- /dev/null Tue May 5 16:32:27 1998 +++ test4 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!test5 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/test5 =================================================================== RCS file: test5 diff -N test5 --- /dev/null Tue May 5 16:32:27 1998 +++ test5 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!file2 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/test6 =================================================================== RCS file: test6 diff -N test6 --- /dev/null Tue May 5 16:32:27 1998 +++ test6 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,13 @@ +!dir1 + + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/xsymlink.c =================================================================== RCS file: xsymlink.c diff -N xsymlink.c --- /dev/null Tue May 5 16:32:27 1998 +++ xsymlink.c Fri Aug 11 08:15:11 2000 @@ -0,0 +1,105 @@ +/* Testsuite for __solve_symlinks() + * There are following success tests: + * 1. Simple case with symlink in current directory + * 2. Recursive symlinks + * 3. Symlink in subdirectory + * 4. Real file in symlinked directory + * 5. Symlink in symlinked directory + * 6. Symlink in symlinked directory to UNIX-style absolute path + * 7. The same with DOS-style absolute path + * 8. Symlink in symlinked directory to absolute path with symlinks itself + * 9. Real file in a symlink subdir in a symlink subdir + * 10. Symlink in a subdir to file in an upper dir + * Any unhandled cases are more than welcome. + * + * And following are failure tests: + * 11. Simple symlink loop. + * 12. Symlink loop across directories + */ +#include +#include +#include +#include +#include +#include + +static void test_success(int num, const char * slink, const char * expect); +static void test_failure(int num, const char * slink); + +int main(void) +{ + if (!__file_exists("test1") || !__file_exists("test4") || + !__file_exists("test5") || !__file_exists("test6") || + !__file_exists("fail1") || !__file_exists("fail2") || + !__file_exists("fail3") || !__file_exists("dir1/fail1") || + !__file_exists("dir1/test1") || !__file_exists("dir1/test2") || + !__file_exists("dir1/test3") || !__file_exists("dir1/test4") || + !__file_exists("dir1/test5") || !__file_exists("dir1/test6") || + !__file_exists("dir1/test7") || access("dir1/dir2", D_OK)) + { + fprintf(stderr, "Required data files not found"); + exit(1); + } + printf("Running readlink() testsuite:\n"); + test_success( 1, "test1", "file1"); + test_success( 2, "test4", "file2"); + test_success( 3, "dir1/test1", "dir1/file1"); + test_success( 4, "test6/file1", "dir1/file1"); + test_success( 5, "test6/test1", "dir1/file1"); + test_success( 6, "test6/test2", "/dev/env/DJDIR/bin/gcc.exe"); + test_success( 7, "test6/test3", "c:\\autoexec.bat"); + test_success( 8, "test6/test4", "c:/dir/file"); + test_success( 9, "test6/test6/file", "dir1/dir2/file"); + /* Following one returns dir1/../file, which is perfectly valid but + forces us to use _fixpath before comparisson */ + test_success(10, "dir1/test7", "file"); + test_failure(11, "fail1"); + test_failure(12, "fail3"); + printf("Done.\n"); + return 0; +} + +static void test_success(int num, const char * slink, const char * expect) +{ + char real_name[FILENAME_MAX + 1]; + char real_fixed[FILENAME_MAX + 1]; + char expect_fixed[FILENAME_MAX + 1]; + char err_buf[50]; + if (!__solve_symlinks(slink, real_name)) + { + sprintf(err_buf, "Test %d failed ", num); + perror(err_buf); + exit(1); + } + _fixpath(expect, expect_fixed); + _fixpath(real_name, real_fixed); + if (strcmp(real_fixed, expect_fixed)) + { + fprintf(stderr, + "Test %d failed - __solve_symlinks returns wrong resolved path\n", + num); + exit(1); + } + printf("Test %d passed\n", num); +} + +static void test_failure(int num, const char * slink) +{ + char buf[FILENAME_MAX + 1]; + char err_buf[50]; + errno = 0; + if (__solve_symlinks(slink, buf)) + { + fprintf(stderr, + "Test %d failed - __solve_symlinks suceeds when it should fail\n", + num); + exit(1); + } + if (errno != ELOOP) + { + sprintf(err_buf, "Test %d failed - wrong errno returned ", num); + perror(err_buf); + exit(1); + } + printf("Test %d passed\n", num); +} Index: djgpp/tests/libc/compat/unistd/dir1/fail1 =================================================================== RCS file: fail1 diff -N fail1 --- /dev/null Tue May 5 16:32:27 1998 +++ fail1 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!../fail3 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test1 =================================================================== RCS file: test1 diff -N test1 --- /dev/null Tue May 5 16:32:27 1998 +++ test1 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!file1 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test2 =================================================================== RCS file: test2 diff -N test2 --- /dev/null Tue May 5 16:32:27 1998 +++ test2 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!/dev/env/DJDIR/bin/gcc.exe + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test3 =================================================================== RCS file: test3 diff -N test3 --- /dev/null Tue May 5 16:32:27 1998 +++ test3 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!c:\autoexec.bat + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test4 =================================================================== RCS file: test4 diff -N test4 --- /dev/null Tue May 5 16:32:27 1998 +++ test4 Fri Aug 11 08:15:11 2000 @@ -0,0 +1,12 @@ +!c:/Programs/test/test6/test5 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test5 =================================================================== RCS file: test5 diff -N test5 --- /dev/null Tue May 5 16:32:27 1998 +++ test5 Fri Aug 11 08:15:12 2000 @@ -0,0 +1,12 @@ +!c:/dir/file + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test6 =================================================================== RCS file: test6 diff -N test6 --- /dev/null Tue May 5 16:32:27 1998 +++ test6 Fri Aug 11 08:15:12 2000 @@ -0,0 +1,12 @@ +!dir2 + + + + + + + + + + + \ No newline at end of file Index: djgpp/tests/libc/compat/unistd/dir1/test7 =================================================================== RCS file: test7 diff -N test7 --- /dev/null Tue May 5 16:32:27 1998 +++ test7 Fri Aug 11 08:15:12 2000 @@ -0,0 +1,12 @@ +!../file + + + + + + + + + + + \ No newline at end of file