Message-ID: <398FD55D.31201350@softhome.net> Date: Tue, 08 Aug 2000 11:39:41 +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: readlink Content-Type: text/plain; charset=iso-8859-4 Content-Transfer-Encoding: 7bit Reply-To: djgpp-workers AT delorie DOT com Here is the first part of actual symlink support. It adds readlink(), its docs and testsuite. For testsuite I've created new tests/libc/compat/unistd directory. And testsuite is A Good Thing (tm) indeed, it helped me to find ~3 bugs in readlink() :) Laurynas Index: djgpp/include/unistd.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/unistd.h,v retrieving revision 1.7 diff -u -r1.7 unistd.h --- unistd.h 2000/07/26 15:17:42 1.7 +++ unistd.h 2000/08/08 09:32:55 @@ -138,6 +138,7 @@ int lchown(const char * file, int owner, int group); offset_t llseek(int _fildes, offset_t _offset, int _whence); int nice(int _increment); +int readlink(const char * __file, char * __buffer, size_t __size); void * sbrk(int _delta); int symlink (const char *, const char *); int sync(void); Index: djgpp/src/docs/kb/wc204.txi =================================================================== RCS file: /cvs/djgpp/djgpp/src/docs/kb/wc204.txi,v retrieving revision 1.16 diff -u -r1.16 wc204.txi --- wc204.txi 2000/08/05 16:52:36 1.16 +++ wc204.txi 2000/08/08 09:32:56 @@ -94,3 +94,7 @@ @code{struct itimerval} set to zero no longer causes the timer to behave as if @code{it_interval.tv_usec} were set to the system clock granularity (55 AT dmn{msec} by default). + +@findex readlink AT r{, and symlink support} +UNIX-style symbolic links are fully emulated by library. As a part of this, +new function @code{readlink} has been added to library. Index: djgpp/src/libc/compat/unistd/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/unistd/makefile,v retrieving revision 1.4 diff -u -r1.4 makefile --- makefile 2000/07/26 15:17:47 1.4 +++ makefile 2000/08/08 09:33:00 @@ -13,6 +13,7 @@ SRC += lchown.c SRC += llseek.c SRC += nice.c +SRC += readlink.c SRC += sync.c SRC += truncate.c SRC += usleep.c Index: djgpp/src/libc/compat/unistd/readlink.c =================================================================== RCS file: readlink.c diff -N readlink.c --- /dev/null Tue May 5 16:32:27 1998 +++ readlink.c Tue Aug 8 05:33:00 2000 @@ -0,0 +1,69 @@ +/* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ +/* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xsymlink.h" + +int readlink(const char * filename, char * buffer, size_t size) +{ + ssize_t bytes_read = 0; + char buf[_SYMLINK_FILE_LEN]; + char * data_buf; + int fd; + struct ffblk file_info; + + /* Reject NULLs */ + if (!filename || !buffer) + { + errno = EINVAL; + return -1; + } + + /* Does symlink file exist at all? */ + if (findfirst(filename, &file_info, 0)) + { + errno = ENOENT; + return -1; + } + + /* Is symlink file size a fixed magic value? */ + if (file_info.ff_fsize != _SYMLINK_FILE_LEN) + { + errno = EINVAL; /* Sad but true */ + return -1; + } + + /* Now we check for special DJGPP symlink format */ + fd = _open(filename, O_RDONLY); + if (fd < 0) + return -1; /* errno from open() call */ + + bytes_read = read(fd, &buf, _SYMLINK_FILE_LEN); + _close(fd); + if (bytes_read == -1) + return -1; /* Return errno set by _read() (_close() in worse case) */ + + /* Check for symlink file header */ + if (strncmp(buf, _SYMLINK_PREFIX, _SYMLINK_PREFIX_LEN)) + { + close(fd); + errno = EINVAL; + return -1; + } + + data_buf = buf + _SYMLINK_PREFIX_LEN; + bytes_read = strchr(data_buf, '\n') - data_buf; + bytes_read = ((unsigned)bytes_read > size) ? size : bytes_read; + memcpy(buffer, data_buf, bytes_read); + return bytes_read; +} + Index: djgpp/src/libc/compat/unistd/readlink.txh =================================================================== RCS file: readlink.txh diff -N readlink.txh --- /dev/null Tue May 5 16:32:27 1998 +++ readlink.txh Tue Aug 8 05:33:00 2000 @@ -0,0 +1,38 @@ +@node readlink, io +@subheading Syntax + +@example +#include + +int readlink(const char *filename, char *buffer, size_t size); +@end example + +@subheading Description +MSDOS doesn't support symbolic links but DJGPP emulates them. +This function checks if @var{filename} is a DJGPP symlink and +the file name that the links points to is copied into buffer, +up to maximum @var{size} characters. Only the last file name +is resolved. +Portable applications should not assume that @var{buffer} is +terminated with @code{'\0'}. + +@subheading Return Value + +Number of copied characters; value -1 is returned in case of +error and @code{errno} is set. When value returned is equal to +@var{size}, you cannot determine if there was enough room to +copy whole name. So increase @var{size} and try again. + +@subheading Portability + +@portability !ansi, !posix + +@subheading Example + +@example +char buf[FILENAME_MAX + 1]; +if (readlink("/dev/env/DJDIR/bin/sh.exe", buf, FILENAME_MAX) == -1) + if (errno == EINVAL) + puts("/dev/env/DJDIR/bin/sh.exe is not a symbolic link."); +@end example + Index: djgpp/src/libc/compat/unistd/xsymlink.h =================================================================== RCS file: xsymlink.h diff -N xsymlink.h --- /dev/null Tue May 5 16:32:27 1998 +++ xsymlink.h Tue Aug 8 05:33:00 2000 @@ -0,0 +1,19 @@ +/* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ +/* Symlink support by Laurynas Biveinis */ + +/* Internal header containing symlink file format specifiers */ +/* I decided not to put it in public header, because this prefix */ +/* isn't guaranteed not to change. */ +#ifndef __XSYMLINK_H_ +#define __XSYMLINK_H_ + +/* Current prefix is for being compatible with CygWin symlinks */ +#define _SYMLINK_PREFIX "!" +#define _SYMLINK_PREFIX_LEN (sizeof(_SYMLINK_PREFIX) - 1) + +/* Symlink files have fixed length - 510 bytes. Why this value? Why not? */ +/* It is big enough to hold longest possible path */ +#define _SYMLINK_FILE_LEN 510 + +#endif /* #ifndef __XSYMLINK_H_ */ + Index: djgpp/tests/libc/compat/unistd/makefile =================================================================== RCS file: makefile diff -N makefile --- /dev/null Tue May 5 16:32:27 1998 +++ makefile Tue Aug 8 05:33:12 2000 @@ -0,0 +1,5 @@ +TOP=../.. + +SRC += readlink.c + +include $(TOP)/../makefile.inc Index: djgpp/tests/libc/compat/unistd/readlink.c =================================================================== RCS file: readlink.c diff -N readlink.c --- /dev/null Tue May 5 16:32:27 1998 +++ readlink.c Tue Aug 8 05:33:12 2000 @@ -0,0 +1,121 @@ +/* Testsuite for readlink() + * There are following tests: + * 1. Simple case with valid symlink + * 2. Check if readlink writes beyond end of result buffer, if filename + * was longer + * 3 & 4. Check if it does not accept NULL args + * 5. Check with not present symlink file + * 6. Check with wrong size, OK contents symlink file + * 7. Check with OK size, wrong contents symlink file + */ + +#include +#include +#include +#include + +static void failure_test(int testno, const char * linkname, char * buf, + int expect_errno, + const char * errmsg1, const char * errmsg2); + +int main(void) +{ + int bytes_read; + char buffer[FILENAME_MAX + 1]; + errno = 0; + + /* Check if we have required files */ + if (!__file_exists("test1") || !__file_exists("test2") || + !__file_exists("test3")) + { + fprintf(stderr, "Cannot run testsuite - required files missing"); + exit(1); + } + + /* Test 1 - simple case with symlink OK */ + bytes_read = readlink("test1", buffer, FILENAME_MAX); + if (bytes_read == -1) + { + perror("Test 1 failed "); + exit(1); + } + /* Is buffer size OK? */ + if (bytes_read != (signed)strlen("file1")) + { + fprintf(stderr, "Test 1 failed - readlink returns wrong buffer size\n"); + exit(1); + } + /* Are buffer contents OK? */ + if (strncmp(buffer, "file1", bytes_read)) + { + fprintf(stderr, "Test 1 failed - readlink returns wrong link value\n"); + exit(1); + } + printf("Test 1 passed\n"); + + /* Test 2 - check if readlink does not overwrite buffer */ + memset(buffer, 0, sizeof(buffer)); + buffer[4] = 0x7F; + bytes_read = readlink("test1", buffer, 4); + /* Is buffer size OK? */ + if (bytes_read != 4) + { + fprintf(stderr, "Test 2 failed - readlink returns wrong buffer size\n"); + exit(1); + } + /* Are buffer contents OK? */ + if (strncmp(buffer, "file", bytes_read)) + { + fprintf(stderr, "Test 2 failed - readlink returns wrong link value\n"); + exit(1); + } + /* Does readlink have security hole? */ + if (buffer[4] != 0x7F) + { + fprintf(stderr, "Test 2 failed - readlink writes beyond the buffer\n"); + exit(1); + } + printf("Test 2 passed\n"); + + /* Tests 3 & 4 - stupid args */ + failure_test(3, NULL, buffer, EINVAL, "readlink accepts NULL arg", + "readlink returns wrong errno for NULL arg"); + failure_test(4, "doesntmatter", NULL, EINVAL, "readlink accepts NULL arg", + "readlink returns wrong errno for NULL arg"); + + /* Test 5 - file not found */ + failure_test(5, "/Pink/Floyd/Animals/Dogs/Shouldnt/Exist", buffer, ENOENT, + "readlink found non-existing file", + "readlink returns wrong errno for non-existing file"); + + /* Test 6: symlink file contents OK, size wrong */ + failure_test(6, "test2", buffer, EINVAL, + "readlink accepted broken symlink file", + "readlink returns wrong errno for broken file"); + + /* Test 7: symlink file size wrong, contents OK */ + failure_test(7, "test3", buffer, EINVAL, + "readlink accepted broken symlink file", + "readlink returns wrong errno for broken file"); + + return 0; +} + +void failure_test(int testno, const char * linkname, char * buf, + int expect_errno, const char * errmsg1, const char * errmsg2) +{ + int bytes_read; + errno = 0; + bytes_read = readlink(linkname, buf, FILENAME_MAX); + if (bytes_read != -1) + { + fprintf(stderr, "Test %d failed - %s\n", testno, errmsg1); + exit(1); + } + if (errno != expect_errno) + { + fprintf(stderr, "Test %d failed - %s\n", testno, errmsg2); + exit(1); + } + printf("Test %d passed\n", testno); +}