Date: Sat, 03 May 2003 15:33:11 +0100 From: "Richard Dawe" Sender: rich AT phekda DOT freeserve DOT co DOT uk To: djgpp-workers AT delorie DOT com X-Mailer: Emacs 21.3.50 (via feedmail 8.3.emacs20_6 I) and Blat ver 1.8.6 Subject: fstat, fd_props and inventing inodes, revision 4 [PATCH] Message-Id: Reply-To: djgpp-workers AT delorie DOT com Hello. Here's revision 4 of the fstat patch to fix the way it invents inodes for files where it can't find an SFT entry. The patch may apply with a little fuzz, because I hacked out some diffs relating only to __tb_size. Changes: * Split out __find_mapping_for_unc into a separate module. This involved adding some __bss_count handling. * Add __find_mapping_to_unc, to map from a drive-letter qualified filename to a UNC filename. * Add documentation for __find_mapping*. * Fix texinfo mark-up. * Fix stat to use the UNC-mapping code too. Mention that we've fixed stat too. I haven't done anything about the memory allocation issue. I don't think we should have a static buffer, because that's about 8K of memory up-front. It will only allocate memory, if it encounters a UNC path. (__find_mapping_for_unc doesn't get the mappings, until it needs to.) This probably isn't the final revision, because: * __find_mapping_to_unc should just return, if the path is already a UNC. * I haven't looked at the problems in _fixpath with UNCs yet. So this is more a WIP. Bye, Rich =] Index: include/dir.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/dir.h,v retrieving revision 1.4 diff -p -c -3 -r1.4 dir.h *** include/dir.h 4 Feb 2003 20:23:00 -0000 1.4 --- include/dir.h 3 May 2003 14:19:14 -0000 *************** struct ffblklfn { *** 75,88 **** #define DIRECTORY 0x08 #define DRIVE 0x10 ! int __file_tree_walk(const char *_dir, int (*_fn)(const char *_path, const struct ffblk *_ff)); ! int findfirst(const char *_pathname, struct ffblk *_ffblk, int _attrib); ! int findnext(struct ffblk *_ffblk); ! void fnmerge (char *_path, const char *_drive, const char *_dir, const char *_name, const char *_ext); ! int fnsplit (const char *_path, char *_drive, char *_dir, char *_name, char *_ext); ! int getdisk(void); ! char * searchpath(const char *_program); ! int setdisk(int _drive); #endif /* !_POSIX_SOURCE */ #endif /* !__STRICT_ANSI__ */ --- 75,94 ---- #define DIRECTORY 0x08 #define DRIVE 0x10 ! typedef struct { ! int drive; ! char share[MAXPATH]; ! } drive_mapping; ! ! int __file_tree_walk(const char *_dir, int (*_fn)(const char *_path, const struct ffblk *_ff)); ! int findfirst(const char *_pathname, struct ffblk *_ffblk, int _attrib); ! int findnext(struct ffblk *_ffblk); ! void fnmerge (char *_path, const char *_drive, const char *_dir, const char *_name, const char *_ext); ! int fnsplit (const char *_path, char *_drive, char *_dir, char *_name, char *_ext); ! int getdisk(void); ! drive_mapping **__get_drive_mappings (void); ! char * searchpath(const char *_program); ! int setdisk(int _drive); #endif /* !_POSIX_SOURCE */ #endif /* !__STRICT_ANSI__ */ Index: include/sys/stat.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/sys/stat.h,v retrieving revision 1.9 diff -p -c -3 -r1.9 stat.h *** include/sys/stat.h 8 Mar 2003 00:40:39 -0000 1.9 --- include/sys/stat.h 3 May 2003 14:19:14 -0000 *************** mode_t umask(mode_t _cmask); *** 89,94 **** --- 89,96 ---- #define S_IFLABEL 0x5000 #define S_ISLABEL(m) (((m) & 0xf000) == 0x5000) + const char * __find_mapping_for_unc(const char *_src, char *_dest); + const char * __find_mapping_to_unc(const char *_src, char *_dest); void _fixpath(const char *, char *); char * __canonicalize_path(const char *, char *, size_t); unsigned short _get_magic(const char *, int); Index: src/libc/dos/dir/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/dos/dir/makefile,v retrieving revision 1.1 diff -p -c -3 -r1.1 makefile *** src/libc/dos/dir/makefile 13 Jun 1995 06:03:36 -0000 1.1 --- src/libc/dos/dir/makefile 3 May 2003 14:19:19 -0000 *************** *** 1,3 **** --- 1,4 ---- + # Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details # Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details TOP=../.. *************** SRC += fnsplit.c *** 8,13 **** --- 9,15 ---- SRC += ftreewlk.c SRC += ftw.c SRC += getdisk.c + SRC += getshare.c SRC += setdisk.c SRC += srchpath.c Index: src/libc/posix/sys/stat/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/posix/sys/stat/makefile,v retrieving revision 1.5 diff -p -c -3 -r1.5 makefile *** src/libc/posix/sys/stat/makefile 8 Mar 2003 00:41:17 -0000 1.5 --- src/libc/posix/sys/stat/makefile 3 May 2003 14:19:24 -0000 *************** SRC += mkfifo.c *** 17,22 **** --- 17,23 ---- SRC += st_loss.c SRC += stat.c SRC += umask.c + SRC += unc_map.c SRC += xstat.c include $(TOP)/../makefile.inc Index: src/libc/posix/sys/stat/fstat.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/posix/sys/stat/fstat.c,v retrieving revision 1.12 diff -p -c -3 -r1.12 fstat.c *** src/libc/posix/sys/stat/fstat.c 23 Apr 2003 19:13:26 -0000 1.12 --- src/libc/posix/sys/stat/fstat.c 3 May 2003 14:19:33 -0000 *************** fstat_assist(int fhandle, struct stat *s *** 381,386 **** --- 381,387 ---- unsigned short trusted_ftime = 0, trusted_fdate = 0; long trusted_fsize = 0; int is_link = 0; + char fixbuf[PATH_MAX]; const char *fd_name = NULL; const char *filename = ""; *************** fstat_assist(int fhandle, struct stat *s *** 437,446 **** stat_buf->st_nlink = 1; /* Get the file name from the file descriptor properties (fd_props), ! * if possible, and fix it up. */ fd_name = __get_fd_name(fhandle); ! if (fd_name != NULL) ! filename = fd_name; /* Get the block size for the device associated with `fhandle'. */ #ifndef NO_ST_BLKSIZE --- 438,456 ---- stat_buf->st_nlink = 1; /* Get the file name from the file descriptor properties (fd_props), ! * if possible, and fix it up. ! * ! * Convert a UNC (\\somemachine\someshare\somefile) to a path ! * with the mapped drive, if any. This ensures that fstat always returns ! * the same information when it invents an inode for the file, ! * irrespective or whether it is accessed via the UNC or a mapped path.*/ fd_name = __get_fd_name(fhandle); ! ! if (fd_name) ! filename = fd_name; ! ! if (*filename) ! filename = __find_mapping_for_unc(filename, fixbuf); /* Get the block size for the device associated with `fhandle'. */ #ifndef NO_ST_BLKSIZE *************** fstat(int handle, struct stat *statbuf) *** 920,926 **** --- 930,940 ---- * use a normal stat call. */ if (__get_fd_flags(handle) & FILE_DESC_DIRECTORY) { + char fixbuf[PATH_MAX]; const char *filename = __get_fd_name(handle); + + if (filename) + filename = __find_mapping_for_unc(filename, fixbuf); if (filename) return stat(filename, statbuf); Index: src/libc/posix/sys/stat/lstat.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/posix/sys/stat/lstat.c,v retrieving revision 1.12 diff -p -c -3 -r1.12 lstat.c *** src/libc/posix/sys/stat/lstat.c 14 Jun 2002 14:26:15 -0000 1.12 --- src/libc/posix/sys/stat/lstat.c 3 May 2003 14:19:43 -0000 *************** *** 1,3 **** --- 1,4 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2002 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 */ *************** *** 12,18 **** * * Note: * ! * The actual function implemented here is lstat(), not stat(). The * stat() now is just a lstat() wrapper. * * Rationale: --- 13,19 ---- * * Note: * ! * The actual function implemented here is lstat(), not stat(). * stat() now is just a lstat() wrapper. * * Rationale: *************** stat_assist(const char *path, struct sta *** 437,442 **** --- 438,444 ---- { struct ffblk ff_blk; char canon_path[MAX_TRUE_NAME]; + char fixbuf[MAX_TRUE_NAME]; char pathname[MAX_TRUE_NAME]; short drv_no; unsigned dos_ftime; *************** stat_assist(const char *path, struct sta *** 558,563 **** --- 560,572 ---- _djstat_fail_bits |= _STFAIL_TRUENAME; } + + /* Convert a UNC (\\somemachine\someshare\somefile) to a path + with the mapped drive, if any. This ensures that stat always returns + the same information when it invents an inode for the file, + irrespective or whether it is accessed via the UNC or a mapped path. */ + if (__find_mapping_for_unc (canon_path, fixbuf) != canon_path) + strcpy(fixbuf, canon_path); /* Call DOS FindFirst function, which will bring us most of the info. */ if (!__findfirst(pathname, &ff_blk, ALL_FILES)) Index: src/docs/kb/wc204.txi =================================================================== RCS file: /cvs/djgpp/djgpp/src/docs/kb/wc204.txi,v retrieving revision 1.154 diff -p -c -3 -r1.154 wc204.txi *** src/docs/kb/wc204.txi 23 Apr 2003 19:20:39 -0000 1.154 --- src/docs/kb/wc204.txi 3 May 2003 14:20:07 -0000 *************** and @code{rewind}, to make them aware of *** 948,959 **** @findex fstat AT r{, and inodes} @findex stat AT r{, and inodes} ! @strong{Simple temporary fix - TODO: more complete fix}: @code{fstat} ! will now use the file name used to open the file, when inventing inodes. ! This is done so that the same inode is generated irrespective ! of the actual file path used to open the file. This also fixes ! the problem where multiple calls to fstat on the same file descriptor ! would give different inodes. @code{stat} and @code{fstat} should now return the same inode for a file in most cases. --- 948,965 ---- @findex fstat AT r{, and inodes} @findex stat AT r{, and inodes} ! @code{fstat} will now use the file name used to open the file, ! when inventing inodes. This is done so that the same inode is generated ! irrespective of the actual file path used to open the file, ! even if the file can be opened through a UNC path ! (@file{\\somemachine\someshare\somefile}) or a mapped drive. ! This fixes the problem where multiple calls to fstat on the same file ! descriptor would give different inodes. ! ! When @code{stat} cannot determine the inode for a file, it will now ensure ! that the same inode is generated, even if the file can be accessed ! through a UNC path (@file{\\somemachine\someshare\somefile}) ! or a mapped drive. @code{stat} and @code{fstat} should now return the same inode for a file in most cases. Index: tests/libc/posix/sys/stat/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/posix/sys/stat/makefile,v retrieving revision 1.5 diff -p -c -3 -r1.5 makefile *** tests/libc/posix/sys/stat/makefile 8 Mar 2003 00:42:43 -0000 1.5 --- tests/libc/posix/sys/stat/makefile 3 May 2003 14:20:07 -0000 *************** *** 1,6 **** --- 1,7 ---- TOP=../../.. SRC += fixpath.c + SRC += fixpath2.c SRC += fstat.c SRC += leak.c SRC += lstat.c *** /dev/null Sat May 3 15:25:38 2003 --- src/libc/dos/dir/getshare.c Sat May 3 14:02:46 2003 *************** *** 0 **** --- 1,188 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + /* LANMan's use_info_1 structure */ + typedef struct { + char local_name[9]; + char padding; + short remote_name_offset; + short remote_name_segment; + short password_offset; + short password_segment; + short status; + short type; + short ignored; + short ignored2; + } share_info __attribute__((packed)); + + #define SHARE_STATUS_OK 0 + #define SHARE_STATUS_DISCONNECTED 2 + + #define SHARE_TYPE_WILDCARD -1 + #define SHARE_TYPE_DISK 0 + #define SHARE_TYPE_PRINT 1 + #define SHARE_TYPE_COM 2 + #define SHARE_TYPE_IPC 3 + + /* Allow for 32 drives plus NULL. */ + #define N_DRIVES 32 + static drive_mapping *drive_mappings[N_DRIVES + 1]; + static share_info *share_infos; + static size_t n_share_infos; + static size_t n_share_infos_max; + + static void + shares_init (void) + { + static int shares_bss_count = -1; + static const size_t default_n_share_infos = N_DRIVES; + size_t i; + + if (shares_bss_count != __bss_count) { + shares_bss_count = __bss_count; + + n_share_infos_max = __tb_size / sizeof(share_info); + + /* Ensure we don't overflow the transfer buffer. We shouldn't + * since it's big enough to store a lot of share info. */ + n_share_infos = default_n_share_infos; + if (n_share_infos > n_share_infos_max) + n_share_infos = n_share_infos_max; + + for (i = 0; i < sizeof(drive_mappings) / sizeof(drive_mappings[0]); i++) { + drive_mappings[i] = NULL; + } + + share_infos = malloc(n_share_infos * sizeof(*share_infos)); + if (share_infos == NULL) + n_share_infos = 0; + } + } + + static int + get_shares_internal (share_info *shares, const size_t n_shares) + { + __dpmi_regs r; + + r.x.ax = 0x5f46; /* LANMan: NetUseEnum */ + r.x.bx = 0x0001; + r.x.cx = sizeof(*shares) * n_shares; + r.x.es = __tb >> 4; + r.x.di = __tb & 0xf; + + __dpmi_int(0x21, &r); + + if(r.x.flags & 1) { + errno = __doserr_to_errno(r.x.ax); + return(-1); + } + + /* Protect from buffer overflow. */ + if (r.x.cx > n_shares) { + errno = ENOMEM; + return(-1); + } + + dosmemget(__tb, r.x.cx * sizeof(*shares), shares); + + return(r.x.cx); + } + + drive_mapping ** + __get_drive_mappings (void) + { + drive_mapping **p = NULL; /* Fail by default. */ + int drive; + char path[MAXPATH]; + int ret, addr, pos, i; + + shares_init(); + + ret = get_shares_internal(share_infos, n_share_infos); + + /* Error or no shares available. */ + if ((ret < 0) || !ret) + return(p); + + for (pos = i = 0; (i < ret) && (pos < N_DRIVES); i++) { + drive = share_infos[i].local_name[0]; + + /* Skip non-disks & disks without a mapping. */ + if ((share_infos[i].type != SHARE_TYPE_DISK) || (drive == '\0')) + continue; + + /* Check the drive is in range. */ + if ((drive >= 'a') && (drive <= 'z')) + drive -= 'a'; + else + drive -= 'A'; + + if (drive >= N_DRIVES) + continue; + + /* Get the UNC - the remote name. */ + addr = share_infos[i].remote_name_segment << 4; + addr += share_infos[i].remote_name_offset; + dosmemget(addr, sizeof(path), path); + path[sizeof(path) - 1] = '\0'; + + if (drive_mappings[pos] == NULL) + drive_mappings[pos] = malloc(sizeof(drive_mapping)); + + if (drive_mappings[pos] != NULL) { + drive_mappings[pos]->drive = drive; + strcpy(drive_mappings[pos]->share, path); + pos++; + } + } + + /* Clean out stale mappings. */ + for (i = pos; i <= N_DRIVES; i++) { + if (drive_mappings[pos] != NULL) { + free(drive_mappings[pos]); + drive_mappings[pos] = NULL; + } + } + + p = drive_mappings; + + return(p); + } + + #ifdef TEST + + #include + + int + main (void) + { + drive_mapping **p = __get_drive_mappings(); + unsigned long i; + + if (p != NULL) { + for (i = 0; p[i] != NULL; i++) { + printf("%c -> %s\n", p[i]->drive + 'A', p[i]->share); + } + } + + #ifdef TEST_PERF + for (i = 0; i < 1000000; i++) { + if (get_shares_internal(share_infos, n_share_infos) < 0) + break; + } + #endif /* TEST_PERF */ + + return(EXIT_SUCCESS); + } + + #endif /* TEST */ *** /dev/null Sat May 3 15:25:38 2003 --- src/libc/dos/dir/getshare.txh Wed Apr 2 13:31:52 2003 *************** *** 0 **** --- 1,54 ---- + @node __get_drive_mappings, dos + @findex __get_drive_mappings + @tindex drive_mapping + @cindex Universal Naming Convention + @cindex UNC + + @subheading Syntax + + @example + #include + + drive_mapping **__get_drive_mappings (void); + @end example + + @subheading Description + + This function returns a list of the shares currently mapped + to drive letters. A share is a @dfn{Universal Naming Convention} (@dfn{UNC}) + path of the form @file{\\machine\share}. + + @code{drive_mapping} is defined as follows: + + @example + typedef struct @{ + int drive; + char share[MAXPATH]; + @} drive_mapping; + @end example + + @code{drive} is the drive number, where @file{A:} is 0, @file{B:} is 1, etc. + @code{share} is the UNC path. + + @subheading Return Value + + NULL, if there are no shares; otherwise a NULL-terminated list + of pointers to @code{drive_mapping}s. The returned list is to static + buffers and should not be modified by caller. + + @subheading Portability + + @portability !ansi, !posix + + @subheading Example + + @example + drive_mapping **p = __get_drive_mappings(); + int i; + + if (p != NULL) @{ + for (i = 0; p[i] != NULL; i++) @{ + printf("%c -> %s\n", p[i]->drive + 'A', p[i]->share); + @} + @} + @end example *** /dev/null Sat May 3 15:25:38 2003 --- src/libc/posix/sys/stat/unc_map.c Sat May 3 15:10:42 2003 *************** *** 0 **** --- 1,206 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ + #include + #include + #include + #include + #include + #include + #include + #include + #include + + /* Holds the last seen value of __bss_count, to be safe for + * restarted programs (emacs). */ + static int mappings_count = 0; + + /* Drive mappings from UNC paths to drive letters. */ + static drive_mapping **drive_mappings; + + /* When were the drive mappings last obtained? */ + static clock_t drive_mappings_time; + + static void + mappings_init (void) + { + if (mappings_count != __bss_count) + { + mappings_count = __bss_count; + drive_mappings = NULL; + } + } + + static void + mappings_check_for_update (void) + { + clock_t t; + + mappings_init(); + + /* + * Update the drive mappings, if: we don't have them; a second has + * elapsed; the clock has wrapped. We update every second as a basic + * caching mechanism, but also to keep up with changes in the mappings. + * + * The mappings may get updated behind this module's back, if the user + * calls __get_drive_mappings, but that should not cause any problems, + * because drive_mappings is a pointer to a static buffer. + */ + t = clock(); + + if ( (drive_mappings == NULL) + || ((drive_mappings_time + CLOCKS_PER_SEC) < t) + || (t < drive_mappings_time)) + { + drive_mappings = __get_drive_mappings(); + drive_mappings_time = clock(); + } + } + + /* + * Find a mapping for the UNC path (\\machine\share) in `src', if one + * exists. If one exists, copy the path into `dest' and convert the UNC + * to the mapped drive, then return `dest'. Otherwise, just return `src'. + */ + + const char * + __find_mapping_for_unc (const char *src, char *dest) + { + int is_unc = 1; + drive_mapping *m = NULL; + int i; + + if (!src[0] || (src[0] && !src[1])) + is_unc = 0; + + if (is_unc && ((src[0] != '\\') || (src[1] != '\\'))) + is_unc = 0; + + if (!is_unc) + return src; + + mappings_check_for_update(); + + if (drive_mappings != NULL) + { + for (i = 0; drive_mappings[i] != NULL; i++) + { + if (!strnicmp(drive_mappings[i]->share, + src, + strlen(drive_mappings[i]->share))) + { + m = drive_mappings[i]; + break; + } + } + } + + if (m == NULL) + return src; + + dest[0] = m->drive + 'A'; + dest[1] = ':'; + dest[2] = '\0'; + strcat(dest, src + strlen(m->share)); + + return dest; + } + + const char * + __find_mapping_to_unc (const char *src, char *dest) + { + int drive = -1; + drive_mapping *m = NULL; + char cwd[FILENAME_MAX]; + char fixbuf[FILENAME_MAX]; + int i; + + if (src[0] && (src[1] == ':')) + { + drive = src[0]; + } + else + { + if (getcwd(cwd, sizeof(cwd)) != NULL) + { + drive = cwd[0]; + } + } + + /* No drive information => just return original. */ + if (drive == -1) + return src; + + /* Find a mapping for this drive, if any. */ + if ((drive >= 'a') || (drive <= 'z')) + drive -= 'a'; + else + drive -= 'A'; + + mappings_check_for_update(); + + for (i = 0; drive_mappings[i] != NULL; i++) + { + if (drive_mappings[i]->drive == drive) + { + m = drive_mappings[i]; + break; + } + } + + if (m == NULL) + return src; + + /* We have a mapping. Fix up the path first, so we can expand + * the drive letter without worrying about relative paths. */ + _fixpath(src, fixbuf); + + if ((strlen(fixbuf) - 2 + strlen(m->share) + 1 /* nul */) > FILENAME_MAX) + { + errno = ENAMETOOLONG; + return NULL; + } + + strcpy(dest, m->share); + strcat(dest, fixbuf + 2); /* skip X: */ + + return dest; + } + + #ifdef TEST + + #include + + int + main (int argc, char *argv[]) + { + char buf[FILENAME_MAX]; + const char *p; + int i; + + for (i = 1; i < argc; i++) + { + p = NULL; + + if (strcmp(argv[i], "--from-unc") == 0) + { + i++; + p = __find_mapping_for_unc(argv[i], buf); + } + else if (strcmp(argv[i], "--to-unc") == 0) + { + i++; + p = __find_mapping_to_unc(argv[i], buf); + } + else + { + fprintf(stderr, "Unknown parameter '%s' - ignoring\n", argv[i]); + } + + if (p != NULL) + printf("%s -> %s\n", argv[i], p); + } + + return EXIT_SUCCESS; + } + + #endif /* TEST */ *** /dev/null Sat May 3 15:25:38 2003 --- src/libc/posix/sys/stat/unc_map.txh Sat May 3 15:22:42 2003 *************** *** 0 **** --- 1,67 ---- + @node __find_mapping_for_unc, file system + @findex __find_mapping_for_unc + + @subheading Syntax + + @example + #include + + const char *__find_mapping_for_unc(const char *src, char *dest); + @end example + + @subheading Description + + This internal function collapses UNC paths like + @file{\\machine\share\somefile.txt} into normal drive-letter-qualified + paths like @file{x:\somefile.txt}, where possible. If no drive mapping + exists for the UNC path, then it will not modify the filename. + + The caller should ensure there is enough space in the buffer pointed to by + @var{dest}. Using ANSI-standard constant @code{FILENAME_MAX} + (defined on @file{stdio.h}) or Posix-standard constant @code{PATH_MAX} + (defined on @file{limits.h}) for the buffer size is recommended. + + @subheading Return Value + + A pointer to the filename, with a drive letter, if possible. If a mapping + was not found, then the original filename is returned. + + @subheading Portability + + @portability !ansi, !posix + + @node __find_mapping_to_unc, file system + @findex __find_mapping_to_unc + + @subheading Syntax + + @example + #include + + const char *__find_mapping_to_unc(const char *src, char *dest); + @end example + + @subheading Description + + This internal function expands drive-letter-qualified paths like + @file{x:\somefile.txt} into UNC paths like + @file{\\machine\share\somefile.txt}, where possible. If no drive mapping + exists for the UNC path, then it will not modify the filename. + + Since the returned path name can be longer than the original one, the + caller should ensure there is enough space in the buffer pointed to by + @var{dest}. Using ANSI-standard constant @code{FILENAME_MAX} + (defined on @file{stdio.h}) or Posix-standard constant @code{PATH_MAX} + (defined on @file{limits.h}) for the buffer size is recommended. + + @subheading Return Value + + A pointer to the filename, converted to a UNC, if possible. If a mapping + was not found, then the original filename is returned. + + If the length of the returned path name exceeds @code{FILENAME_MAX}, + @code{NULL} is returned and @code{errno} is set to @code{ENAMETOOLONG}. + + @subheading Portability + + @portability !ansi, !posix