diff options
Diffstat (limited to 'winsup/cygwin/path.cc')
-rw-r--r-- | winsup/cygwin/path.cc | 2836 |
1 files changed, 2836 insertions, 0 deletions
diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc new file mode 100644 index 0000000..8fcfb46 --- /dev/null +++ b/winsup/cygwin/path.cc @@ -0,0 +1,2836 @@ +/* path.cc: path support. + + Copyright 1996, 1997, 1998, 1999, 2000 Cygnus Solutions. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +/* This module's job is to + - convert between POSIX and Win32 style filenames, + - support the `mount' functionality, + - support symlinks for files and directories + + Pathnames are handled as follows: + + - / is equivalent to \ + - Paths beginning with // (or \\) are not translated (i.e. looked + up in the mount table) and are assumed to be UNC path names. + - Paths containing a : are not translated (paths like + /foo/bar/baz:qux: don't make much sense but having the rule written + this way allows one to use strchr). + + The goal in the above set of rules is to allow both POSIX and Win32 + flavors of pathnames without either interfering. The rules are + intended to be as close to a superset of both as possible. + + A possible future enhancement would be to allow people to + disable/enable the mount table handling to support pure Win32 + pathnames. Hopefully this won't be needed. The suggested way to + do this would be an environment variable because + a) we need something that is inherited from parent to child, + b) environment variables can be passed from the DOS shell to a + cygwin app, + c) it allows disabling the feature on an app by app basis within + the same session (whereas playing about with the registry wouldn't + -- without getting too complicated). Example: + CYGWIN=pathrules[=@]{win32,posix}. If CYGWIN=pathrules=win32, + mount table handling is disabled. [The intent is to have CYGWIN be + a catchall for tweaking various cygwin.dll features]. + + Note that you can have more than one path to a file. The mount + table is always prefered when translating Win32 paths to POSIX + paths. Win32 paths in mount table entries may be UNC paths or + standard Win32 paths starting with <drive-letter>: + + In converting from a Win32 to a POSIX pathname, if there is no + mount point that will allow the conversion to take place, a user + mount point will be automatically created under + cygdrive/<drive> and the translation will be redone, this + time successfully. + + Text vs Binary issues are not considered here in path style + decisions. + + / and \ are treated as equivalent. One or the other is prefered in + certain situations (e.g. / is preferred in result of getcwd, \ is + preferred in arguments to Win32 api calls), but this code will + translate as necessary. + + Apps wishing to translate to/from pure Win32 and POSIX-like + pathnames can use cygwin_foo. + + Removing mounted filesystem support would simplify things greatly, + but having it gives us a mechanism of treating disk that lives on a + UNIX machine as having UNIX semantics [it allows one to edit a text + file on that disk and not have cr's magically appear and perhaps + break apps running on UNIX boxes]. It also useful to be able to + layout a hierarchy without changing the underlying directories. + + The semantics of mounting file systems is not intended to precisely + follow normal UNIX systems. + + Each DOS drive is defined to have a current directory. Supporting + this would complicate things so for now things are defined so that + c: means c:\. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <mntent.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include "winsup.h" +#include <ctype.h> + +static int symlink_check_one (const char *path, char *buf, int buflen, + DWORD& fileattr, unsigned *pflags, + const suffix_info *suffixes, + char *&found_suffix); +static int normalize_win32_path (const char *cwd, const char *src, char *dst); +static char *getcwd_inner (char *buf, size_t ulen, int posix_p); +static void slashify (const char *src, char *dst, int trailing_slash_p); +static void backslashify (const char *src, char *dst, int trailing_slash_p); +static int path_prefix_p_ (const char *path1, const char *path2, int len1); +static int get_current_directory_name (); + +static NO_COPY const char escape_char = '^'; + +/********************** Path Helper Functions *************************/ + +#define path_prefix_p(p1, p2, l1) \ + ((tolower(*(p1))==tolower(*(p2))) && \ + path_prefix_p_(p1, p2, l1)) + +#define SYMLINKATTR(x) \ + (((x) & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY)) == \ + FILE_ATTRIBUTE_SYSTEM) + +/* Return non-zero if PATH1 is a prefix of PATH2. + Both are assumed to be of the same path style and / vs \ usage. + Neither may be "". + LEN1 = strlen (PATH1). It's passed because often it's already known. + + Examples: + /foo/ is a prefix of /foo <-- may seem odd, but desired + /foo is a prefix of /foo/ + / is a prefix of /foo/bar + / is not a prefix of foo/bar + foo/ is a prefix foo/bar + /foo is not a prefix of /foobar +*/ + +/* Determine if path prefix matches current cygdrive */ +#define iscygdrive(path) \ + (path_prefix_p (cygwin_shared->mount.cygdrive, (path), cygwin_shared->mount.cygdrive_len)) + +#define iscygdrive_device(path) \ + (iscygdrive(path) && isalpha(path[cygwin_shared->mount.cygdrive_len]) && \ + (isdirsep(path[cygwin_shared->mount.cygdrive_len + 1]) || \ + !path[cygwin_shared->mount.cygdrive_len + 1])) + +/******************** Directory-related Support **************************/ + +/* Cache getcwd value. FIXME: We need a lock for these in order to + support multiple threads. */ + +#ifdef _MT_SAFE +#define current_directory_name _reent_winsup()->_current_directory_name +#define current_directory_posix_name _reent_winsup()->_current_directory_posix_name +#define current_directory_hash _reent_winsup()->_current_directory_hash +#else +static char *current_directory_name; +static char *current_directory_posix_name; +static unsigned long current_directory_hash; +#endif + +static int +path_prefix_p_ (const char *path1, const char *path2, int len1) +{ + /* Handle case where PATH1 has trailing '/' and when it doesn't. */ + if (len1 > 0 && SLASH_P (path1[len1 - 1])) + len1--; + + if (len1 == 0) + return SLASH_P (path2[0]) && !SLASH_P (path2[1]); + + if (!strncasematch (path1, path2, len1)) + return 0; + + return SLASH_P (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':'; +} + +/* Convert an arbitrary path SRC to a pure Win32 path, suitable for + passing to Win32 API routines. + + If an error occurs, `error' is set to the errno value. + Otherwise it is set to 0. + + follow_mode values: + SYMLINK_FOLLOW - convert to PATH symlink points to + SYMLINK_NOFOLLOW - convert to PATH of symlink itself + SYMLINK_IGNORE - do not check PATH for symlinks + SYMLINK_CONTENTS - just return symlink contents +*/ + +path_conv::path_conv (const char *src, symlink_follow follow_mode, + int use_full_path, const suffix_info *suffixes) +{ + /* This array is used when expanding symlinks. It is MAX_PATH * 2 + in length so that we can hold the expanded symlink plus a + trailer. */ + char work_buf[MAX_PATH * 3 + 3]; + char tmp_buf[MAX_PATH]; + char path_buf[MAX_PATH]; + + char *rel_path, *full_path; + + if ((error = check_null_empty_path (src))) + return; + + if (use_full_path) + rel_path = path_buf, full_path = this->path; + else + rel_path = this->path, full_path = path_buf; + + char *sym_buf = work_buf + MAX_PATH + 1; + /* This loop handles symlink expansion. */ + int loop = 0; + path_flags = 0; + known_suffix = NULL; + fileattr = (DWORD) -1; + for (;;) + { + MALLOC_CHECK; + /* Must look up path in mount table, etc. */ + error = cygwin_shared->mount.conv_to_win32_path (src, rel_path, + full_path, + devn, unit, &path_flags); + MALLOC_CHECK; + if (error != 0) + return; + if (devn != FH_BAD) + { + fileattr = 0; + return; + } + + /* Eat trailing slashes */ + char *tail = strchr (full_path, '\0'); + /* If path is only a drivename, Windows interprets it as + the current working directory on this drive instead of + the root dir which is what we want. So we need + the trailing backslash in this case. */ + while (tail > full_path + 3 && (*--tail == '\\')) + *tail = '\0'; + if (full_path[0] && full_path[1] == ':' && full_path[2] == '\0') + strcat (full_path, "\\"); + + if (follow_mode == SYMLINK_IGNORE) + { + fileattr = GetFileAttributesA (path); + goto out; + } + + /* Make a copy of the path that we can munge up */ + char path_copy[strlen (full_path) + 2]; + strcpy (path_copy, full_path); + + tail = path_copy + 1 + (tail - full_path); // Point to end of copy + + *sym_buf = '\0'; // Paranoid + + /* Scan path_copy from right to left looking either for a symlink + or an actual existing file. If an existing file is found, just + return. If a symlink is found exit the for loop. + Also: be careful to preserve the errno returned from + symlink_check_one as the caller may need it. */ + /* FIXME: Do we have to worry about multiple \'s here? */ + int component = 0; // Number of translated components + DWORD attr; + for (;;) + { + save_errno s (0); + unsigned dummy_flags, *fp; + const suffix_info *suff; + + /* Don't allow symlink_check_one to set anything in the path_conv + class if we're working on an inner component of the path */ + if (component) + { + fp = &dummy_flags; + suff = NULL; + } + else + { + fp = &path_flags; + suff = suffixes; + } + MALLOC_CHECK; + int len = symlink_check_one (path_copy, sym_buf, MAX_PATH, attr, + fp, suff, known_suffix); + MALLOC_CHECK; + + /* If symlink_check_one found an existing non-symlink file, then + it returns a length of 0 and sets errno to EINVAL. It also sets + any suffix found into `sym_buf'. */ + if (!len && get_errno () == EINVAL) + { + if (component == 0) + { + fileattr = attr; + if (follow_mode == SYMLINK_CONTENTS) + goto out; + else if (*sym_buf) + { + known_suffix = strchr (this->path, '\0'); + strcpy (known_suffix, sym_buf); + } + else if (known_suffix) + known_suffix = this->path + (known_suffix - path_copy); + } + goto out; // file found + } + /* Found a symlink if len > 0. If component == 0, then the + src path itself was a symlink. If !follow_mode then + we're done. Otherwise we have to insert the path found + into the full path that we are building and perform all of + these operations again on the newly derived path. */ + else if (len > 0) + { + if (component == 0) + { + if (follow_mode != SYMLINK_FOLLOW) + { + set_symlink (); // last component of path's a symlink. + fileattr = attr; + if (follow_mode == SYMLINK_CONTENTS) + strcpy (path, sym_buf); + goto out; + } + } + break; + } + + s.reset (); // remember errno from symlink_check_one + + if (!(tail = strrchr (path_copy, '\\')) || + (tail > path_copy && tail[-1] == ':')) + goto out; // all done + + /* Haven't found a valid pathname component yet. + Pinch off the tail and try again. */ + *tail = '\0'; + component++; + } + + /* Arrive here if above loop detected a symlink. */ + if (++loop > MAX_LINK_DEPTH) + { + error = ELOOP; // Eep. + return; + } + MALLOC_CHECK; + + tail = full_path + (tail - path_copy); + int taillen = strlen (tail); + int buflen = strlen (sym_buf); + if (buflen + taillen > MAX_PATH) + { + error = ENAMETOOLONG; + strcpy (path, "::ENAMETOOLONG::"); + return; + } + + /* Copy tail of full_path to discovered symlink. */ + char *p; + for (p = sym_buf + buflen; *tail; tail++) + *p++ = *tail == '\\' ? '/' : *tail; + *p = '\0'; + + /* If symlink referred to an absolute path, then we + just use sym_buf and loop. Otherwise tack the head of + path_copy before sym_buf and translate it back from a + Win32-style path to a POSIX-style one. */ + if (isabspath (sym_buf)) + src = sym_buf; + else if (!(tail = strrchr (path_copy, '\\'))) + system_printf ("problem parsing %s - '%s'", src, full_path); + else + { + int headlen = 1 + tail - path_copy; + p = sym_buf - headlen; + memcpy (p, path_copy, headlen); + MALLOC_CHECK; + error = cygwin_shared->mount.conv_to_posix_path (p, tmp_buf, 1); + MALLOC_CHECK; + if (error) + return; + src = tmp_buf; + } + } +out: + DWORD serial, volflags; + + char root[strlen(full_path) + 10]; + strcpy (root, full_path); + if (!rootdir (root) || + !GetVolumeInformation (root, NULL, 0, &serial, NULL, &volflags, NULL, 0)) + set_has_acls (FALSE); + else + set_has_acls (volflags & FS_PERSISTENT_ACLS); +} + +#define deveq(s) (strcasematch (name, (s))) +#define deveqn(s, n) (strncasematch (name, (s), (n))) + +static __inline int +digits (const char *name) +{ + char *p; + int n = strtol(name, &p, 10); + + return p > name && !*p ? n : -1; +} + +const char *windows_device_names[] = +{ + NULL, + "\\dev\\console", + "conin", + "conout", + "\\dev\\ttym", + "\\dev\\tty%d", + "\\dev\\ptym", + "\\\\.\\com%d", + "\\dev\\pipe", + "\\dev\\piper", + "\\dev\\pipew", + "\\dev\\socket", + "\\dev\\windows", + + NULL, NULL, NULL, + + "\\dev\\disk", + "\\dev\\fd%d", + "\\dev\\st%d", + "nul", + "\\dev\\zero", +}; + +static int +get_raw_device_number (const char *uxname, const char *w32path, int &unit) +{ + DWORD devn = FH_BAD; + + if (strncasecmp (w32path, "\\\\.\\tape", 8) == 0) + { + devn = FH_TAPE; + unit = digits (w32path + 8); + // norewind tape devices have leading n in name + if (! strncasecmp (uxname, "/dev/n", 6)) + unit += 128; + } + else if (isalpha (w32path[4]) && w32path[5] == ':') + { + devn = FH_FLOPPY; + unit = tolower (w32path[4]) - 'a'; + } + else if (strncasecmp (w32path, "\\\\.\\physicaldrive", 17) == 0) + { + devn = FH_FLOPPY; + unit = digits (w32path + 17) + 128; + } + return devn; +} + +int __stdcall +get_device_number (const char *name, int &unit, BOOL from_conv) +{ + DWORD devn = FH_BAD; + unit = 0; + + if ((*name == '/' && deveqn ("/dev/", 5)) || + (*name == '\\' && deveqn ("\\dev\\", 5))) + { + name += 5; + if (deveq ("tty")) + { + if (tty_attached (myself)) + { + unit = myself->ctty; + devn = FH_TTYS; + } + else if (myself->ctty > 0) + devn = FH_CONSOLE; + } + else if (deveqn ("tty", 3) && (unit = digits (name + 3)) >= 0) + devn = FH_TTYS; + else if (deveq ("ttym")) + devn = FH_TTYM; + else if (deveq ("ptmx")) + devn = FH_PTYM; + else if (deveq ("windows")) + devn = FH_WINDOWS; + else if (deveq ("conin")) + devn = FH_CONIN; + else if (deveq ("conout")) + devn = FH_CONOUT; + else if (deveq ("null")) + devn = FH_NULL; + else if (deveq ("zero")) + devn = FH_ZERO; + else if (deveqn ("com", 3) && (unit = digits (name + 3)) >= 0) + devn = FH_SERIAL; + else if (deveq ("pipe") || deveq ("piper") || deveq ("pipew")) + devn = FH_PIPE; + else if (deveq ("tcp") || deveq ("udp") || deveq ("streamsocket") + || deveq ("dgsocket")) + devn = FH_SOCKET; + else if (! from_conv) + devn = get_raw_device_number (name - 5, + path_conv (name - 5, + SYMLINK_IGNORE).get_win32 (), + unit); + } + else if (deveqn ("com", 3) && (unit = digits (name + 3)) >= 0) + devn = FH_SERIAL; + + return devn; +} + +/* Return TRUE if src_path is a Win32 device name, filling out the device + name in win32_path */ + +static BOOL +win32_device_name (const char *src_path, char *win32_path, + DWORD &devn, int &unit) +{ + const char *devfmt; + + devn = get_device_number (src_path, unit, TRUE); + + if (devn == FH_BAD) + return FALSE; + + if ((devfmt = windows_device_names[FHDEVN (devn)]) == NULL) + return FALSE; + __small_sprintf (win32_path, devfmt, unit); + return TRUE; +} + +/* Normalize a POSIX path. + \'s are converted to /'s in the process. + All duplicate /'s, except for 2 leading /'s, are deleted. + The result is 0 for success, or an errno error value. */ + +static __inline int +normalize_posix_path (const char *cwd, const char *src, char *dst) +{ + const char *src_start = src; + char *dst_start = dst; + + if (! SLASH_P (src[0])) + { + if (strlen (cwd) + 1 + strlen (src) >= MAX_PATH) + { + debug_printf ("ENAMETOOLONG = normalize_posix_path (%s)", src); + return ENAMETOOLONG; + } + strcpy (dst, cwd); + dst = strchr (dst, '\0'); + if (dst > dst_start && !isdirsep(dst[-1])) + *dst++ = '/'; + } + /* Two leading /'s? If so, preserve them. */ + else if (SLASH_P (src[1])) + { + *dst++ = '/'; + *dst++ = '/'; + src += 2; + if (SLASH_P(*src)) + { /* Starts with three or more slashes - reset. */ + dst = dst_start; + *dst++ = '/'; + src = src_start + 1; + } + } + + while (*src) + { + /* Strip runs of /'s. */ + if (SLASH_P (*src)) + { + *dst++ = '/'; + src++; + while (SLASH_P(*src)) + src++; + } + /* Ignore "./". */ + else if (src[0] == '.' && SLASH_P (src[1]) + && (src == src_start || SLASH_P (src[-1]))) + { + src += 2; + while(SLASH_P(src[0])) + src++; + } + /* Backup if "..". */ + else if (src[0] == '.' && src[1] == '.' + /* dst must be greater than dst_start */ + && isdirsep (dst[-1]) + && (SLASH_P (src[2]) || src[2] == 0)) + { + /* Back up over /, but not if it's the first one. */ + if (dst > dst_start + 1) + dst--; + /* Now back up to the next /. */ + while (dst > dst_start + 1 && !isdirsep (dst[-1])) + dst--; + src += 2; + while (SLASH_P (*src)) + src++; + } + /* Otherwise, add char to result. */ + else + { + if (*src == '\\') + *dst++ = '/'; + else + *dst++ = *src; + ++src; + } + } + *dst = 0; + debug_printf ("%s = normalize_posix_path (%s)", dst_start, src_start); + return 0; +} + +/* Normalize a Win32 path. + /'s are converted to \'s in the process. + All duplicate \'s, except for 2 leading \'s, are deleted. + + The result is 0 for success, or an errno error value. + FIXME: A lot of this should be mergeable with the POSIX critter. */ + +static int +normalize_win32_path (const char *cwd, const char *src, char *dst) +{ + const char *src_start = src; + char *dst_start = dst; + + if (! SLASH_P (src[0]) + && strchr (src, ':') == NULL) + { + if (strlen (cwd) + 1 + strlen (src) >= MAX_PATH) + { + debug_printf ("ENAMETOOLONG = normalize_win32_path (%s)", src); + return ENAMETOOLONG; + } + strcpy (dst, cwd); + dst += strlen (dst); + *dst++ = '\\'; + } + /* Two leading \'s? If so, preserve them. */ + else if (SLASH_P (src[0]) && SLASH_P (src[1])) + { + *dst++ = '\\'; + ++src; + } + + while (*src) + { + /* Strip duplicate /'s. */ + if (SLASH_P (src[0]) && SLASH_P (src[1])) + src++; + /* Ignore "./". */ + else if (src[0] == '.' && SLASH_P (src[1]) + && (src == src_start || SLASH_P (src[-1]))) + { + src += 2; + } + + /* Backup if "..". */ + else if (src[0] == '.' && src[1] == '.' + /* dst must be greater than dst_start */ + && dst[-1] == '\\' + && (SLASH_P (src[2]) || src[2] == 0)) + { + /* Back up over /, but not if it's the first one. */ + if (dst > dst_start + 1) + dst--; + /* Now back up to the next /. */ + while (dst > dst_start + 1 && dst[-1] != '\\' && dst[-2] != ':') + dst--; + src += 2; + if (SLASH_P (*src)) + src++; + } + /* Otherwise, add char to result. */ + else + { + if (*src == '/') + *dst++ = '\\'; + else + *dst++ = *src; + ++src; + } + } + *dst = 0; + debug_printf ("%s = normalize_win32_path (%s)", dst_start, src_start); + return 0; +} + + +/* Various utilities. */ + +/* slashify: Convert all back slashes in src path to forward slashes + in dst path. Add a trailing slash to dst when trailing_slash_p arg + is set to 1. */ + +static void +slashify (const char *src, char *dst, int trailing_slash_p) +{ + const char *start = src; + + while (*src) + { + if (*src == '\\') + *dst++ = '/'; + else + *dst++ = *src; + ++src; + } + if (trailing_slash_p + && src > start + && !isdirsep (src[-1])) + *dst++ = '/'; + *dst++ = 0; +} + +/* backslashify: Convert all forward slashes in src path to back slashes + in dst path. Add a trailing slash to dst when trailing_slash_p arg + is set to 1. */ + +static void +backslashify (const char *src, char *dst, int trailing_slash_p) +{ + const char *start = src; + + while (*src) + { + if (*src == '/') + *dst++ = '\\'; + else + *dst++ = *src; + ++src; + } + if (trailing_slash_p + && src > start + && !isdirsep (src[-1])) + *dst++ = '\\'; + *dst++ = 0; +} + +/* nofinalslash: Remove trailing / and \ from SRC (except for the + first one). It is ok for src == dst. */ + +void __stdcall +nofinalslash (const char *src, char *dst) +{ + int len = strlen (src); + if (src != dst) + memcpy (dst, src, len + 1); + while (len > 1 && SLASH_P (dst[--len])) + dst[len] = '\0'; +} + +/* slash_drive_prefix_p: Return non-zero if PATH begins with + //<letter>. */ + +static int +slash_drive_prefix_p (const char *path) +{ + return (isdirsep(path[0]) + && isdirsep(path[1]) + && isalpha (path[2]) + && (path[3] == 0 || path[3] == '/')); +} + +/* slash_unc_prefix_p: Return non-zero if PATH begins with //UNC/SHARE */ + +int __stdcall +slash_unc_prefix_p (const char *path) +{ + char *p = NULL; + int ret = (isdirsep (path[0]) + && isdirsep (path[1]) + && isalpha (path[2]) + && path[3] != 0 + && !isdirsep (path[3]) + && ((p = strchr(&path[3], '/')) != NULL)); + if (!ret || p == NULL) + return ret; + return ret && isalnum (p[1]); +} + +/* conv_path_list: Convert a list of path names to/from Win32/POSIX. + + SRC is not a const char * because we temporarily modify it to ease + the implementation. + + I believe Win32 always has '.' in $PATH. POSIX obviously doesn't. + We certainly don't want to handle that here, but it is something for + the caller to think about. */ + +static void +conv_path_list (const char *src, char *dst, int to_posix_p) +{ + char *s; + char *d = dst; + char src_delim = to_posix_p ? ';' : ':'; + char dst_delim = to_posix_p ? ':' : ';'; + int (*conv_fn) (const char *, char *) = (to_posix_p + ? cygwin_conv_to_posix_path + : cygwin_conv_to_win32_path); + + do + { + s = strchr (src, src_delim); + if (s) + { + *s = 0; + (*conv_fn) (src[0] != 0 ? src : ".", d); + d += strlen (d); + *d++ = dst_delim; + *s = src_delim; + src = s + 1; + } + else + { + /* Last one. */ + (*conv_fn) (src[0] != 0 ? src : ".", d); + } + } + while (s != NULL); +} + +/************************* mount_info class ****************************/ + +/* init: Initialize the mount table. */ + +void +mount_info::init () +{ + int found_slash = 0; + + nmounts = 0; + had_to_create_mount_areas = 0; + + /* Fetch the mount table and cygdrive-related information from + the registry. */ + from_registry (); + + /* If slash isn't already mounted, mount system directory as slash. */ + if (nmounts != 0) + for (int i = 0; i < nmounts; i++) + { + if (strcmp (mount[i].posix_path, "/") == 0) + { + found_slash = 1; + break; + } + } + + if (!found_slash) + mount_slash (); +} + +/* mount_slash: mount the system partition as slash. */ + +void +mount_info::mount_slash () +{ + char drivestring[MAX_PATH]; + GetSystemDirectory (drivestring, MAX_PATH); + drivestring[2] = 0; /* truncate path to "<drive>:" */ + + if (add_reg_mount (drivestring, "/", 0) == 0) + add_item (drivestring, "/", 0); +} + +/* conv_to_win32_path: Ensure src_path is a pure Win32 path and store + the result in win32_path. + + If win32_path != NULL, the relative path, if possible to keep, is + stored in win32_path. If the relative path isn't possible to keep, + the full path is stored. + + If full_win32_path != NULL, the full path is stored there. + + The result is zero for success, or an errno value. + + {,full_}win32_path must have sufficient space (i.e. MAX_PATH bytes). */ + +int +mount_info::conv_to_win32_path (const char *src_path, char *win32_path, + char *full_win32_path, DWORD &devn, int &unit, unsigned *flags) +{ + int src_path_len = strlen (src_path); + int trailing_slash_p = (src_path_len > 0 + && SLASH_P (src_path[src_path_len - 1])); + MALLOC_CHECK; + int isrelpath; + unsigned dummy_flags; + + devn = FH_BAD; + unit = 0; + + if (!flags) + flags = &dummy_flags; + + *flags = 0; + debug_printf ("conv_to_win32_path (%s)", src_path); + + if (src_path_len >= MAX_PATH) + { + debug_printf ("ENAMETOOLONG = conv_to_win32_path (%s)", src_path); + return ENAMETOOLONG; + } + + int i, rc; + char *dst = NULL; + mount_item *mi = NULL; /* initialized to avoid compiler warning */ + char pathbuf[MAX_PATH]; + + /* The rule is :'s can't appear in [our] POSIX path names so this is a safe + test; if ':' is present it already be in Win32 form. */ + if (strchr (src_path, ':') != NULL) + { + debug_printf ("%s already win32", src_path); + rc = normalize_win32_path ("", src_path, pathbuf); + if (rc) + return rc; + /* FIXME: Do we have to worry about trailing_slash_p here? */ + if (win32_path != NULL) + strcpy (win32_path, pathbuf); + if (full_win32_path != NULL) + strcpy (full_win32_path, pathbuf); + *flags = set_flags_from_win32_path (pathbuf); + goto out; + } + + /* Normalize the path, taking out ../../ stuff, we need to do this + so that we can move from one mounted directory to another with relative + stuff. + + eg mounting c:/foo /foo + d:/bar /bar + + cd /bar + ls ../foo + + should look in c:/foo, not d:/foo. + + We do this by first getting an absolute UNIX-style path and then + converting it to a DOS-style path, looking up the appropriate drive + in the mount table. */ + + char cwd[MAX_PATH]; + + /* No need to fetch cwd if path is absolute. */ + if ((isrelpath = ! SLASH_P (*src_path))) + getcwd_inner (cwd, MAX_PATH, TRUE); /* FIXME: check rc */ + else + strcpy (cwd, "/"); /* some innocuous value */ + + rc = normalize_posix_path (cwd, src_path, pathbuf); + MALLOC_CHECK; + if (rc != 0) + { + debug_printf ("%d = conv_to_win32_path (%s)", rc, src_path); + *flags = 0; + return rc; + } + nofinalslash (pathbuf, pathbuf); + + /* Determine where the destination should be placed. */ + if (full_win32_path != NULL) + dst = full_win32_path; + else if (win32_path != NULL) + dst = win32_path; + + if (dst == NULL) + goto out; /* Sanity check. */ + + /* See if this is a cygwin "device" */ + if (win32_device_name (pathbuf, dst, devn, unit)) + { + *flags = MOUNT_BINARY; /* FIXME: Is this a sensible default for devices? */ + goto fillin; + } + + /* Check if the cygdrive prefix was specified. If so, just strip + off the prefix and transform it into an MS-DOS path. */ + MALLOC_CHECK; + if (iscygdrive_device (pathbuf)) + { + if (!cygdrive_win32_path (pathbuf, dst, trailing_slash_p)) + return ENOENT; + *flags = cygdrive_flags; + goto fillin; + } + + /* Check the mount table for prefix matches. */ + for (i = 0; i < nmounts; i++) + { + mi = mount + posix_sorted[i]; + if (path_prefix_p (mi->posix_path, pathbuf, mi->posix_pathlen)) + break; + } + + if (i >= nmounts) + { + if (slash_drive_prefix_p (pathbuf)) + slash_drive_to_win32_path (pathbuf, dst, trailing_slash_p); + else + backslashify (src_path, dst, trailing_slash_p); /* just convert */ + *flags = 0; + } + else + { + int n = mi->native_pathlen; + memcpy (dst, mi->native_path, n); + char *p = pathbuf + mi->posix_pathlen; + if (!trailing_slash_p && !*p) + dst[n] = '\0'; + else + { + /* Do not add trailing \ to UNC device names like \\.\a: */ + if (*p != '/' && /* FIXME: this test seems wrong. */ + (strncmp (mi->native_path, "\\\\.\\", 4) != 0 || + strncmp (mi->native_path + 4, "UNC\\", 4) == 0)) + dst[n++] = '\\'; + strcpy (dst + n, p); + } + backslashify (dst, dst, trailing_slash_p); + *flags = mi->flags; + } + +fillin: + /* Compute relative path if asked to and able to. */ + unsigned cwdlen; + cwdlen = 0; /* avoid a (hopefully) bogus compiler warning */ + if (win32_path == NULL) + /* nothing to do */; + else if (isrelpath && + path_prefix_p (current_directory_name, dst, + cwdlen = strlen (current_directory_name))) + { + if (strlen (dst) == cwdlen) + dst += cwdlen; + else + dst += isdirsep (current_directory_name[cwdlen - 1]) ? cwdlen : cwdlen + 1; + + memmove (win32_path, dst, strlen (dst) + 1); + if (!*win32_path) + { + strcpy (win32_path, "."); + if (trailing_slash_p) + strcat (win32_path, "\\"); + } + } + else if (win32_path != dst) + strcpy (win32_path, dst); + +out: + MALLOC_CHECK; + debug_printf ("%s(rel), %s(abs) %p(flags) = conv_to_win32_path (%s)", + win32_path, full_win32_path, *flags, + src_path); + return 0; +} + +/* Convert PATH (for which slash_drive_prefix_p returns 1) to WIN32 form. */ + +void +mount_info::slash_drive_to_win32_path (const char *path, char *buf, + int trailing_slash_p) +{ + buf[0] = path[2]; + buf[1] = ':'; + if (path[3] == '0') + strcpy (buf + 2, "\\"); + else + backslashify (path + 3, buf + 2, trailing_slash_p); +} + +/* cygdrive_posix_path: Build POSIX path used as the + mount point for cygdrives created when there is no other way to + obtain a POSIX path from a Win32 one. */ + +void +mount_info::cygdrive_posix_path (const char *src, char *dst, int trailing_slash_p) +{ + int len = cygdrive_len; + + memcpy (dst, cygdrive, len + 1); + + /* Now finish the path off with the drive letter to be used. + The cygdrive prefix always ends with a trailing slash so + the drive letter is added after the path. */ + dst[len++] = tolower (src[0]); + if (!src[2]) + dst[len++] = '\000'; + else + { + dst[len++] = '/'; + strcpy (dst + len, src + 3); + } + slashify (dst, dst, trailing_slash_p); +} + +int +mount_info::cygdrive_win32_path (const char *src, char *dst, int trailing_slash_p) +{ + const char *p = src + cygdrive_len; + if (!isalpha (*p) || (!isdirsep (p[1]) && p[1])) + return 0; + dst[0] = *p; + dst[1] = ':'; + strcpy (dst + 2, p + 1); + backslashify (dst, dst, trailing_slash_p || !dst[2]); + debug_printf ("src '%s', dst '%s'", src, dst); + return 1; +} + +/* conv_to_posix_path: Ensure src_path is a POSIX path. + + The result is zero for success, or an errno value. + posix_path must have sufficient space (i.e. MAX_PATH bytes). + If keep_rel_p is non-zero, relative paths stay that way. */ + +int +mount_info::conv_to_posix_path (const char *src_path, char *posix_path, + int keep_rel_p) +{ + int src_path_len = strlen (src_path); + int trailing_slash_p = (src_path_len > 0 + && SLASH_P (src_path[src_path_len - 1])); + int relative_path_p = (! SLASH_P (*src_path) + && strchr (src_path, ':') == NULL); + + debug_printf ("conv_to_posix_path (%s, %s)", src_path, + keep_rel_p ? "keep-rel" : "no-keep-rel"); + MALLOC_CHECK; + + if (src_path_len >= MAX_PATH) + { + debug_printf ("ENAMETOOLONG"); + return ENAMETOOLONG; + } + + /* FIXME: For now, if the path is relative and it's supposed to stay + that way, skip mount table processing. */ + MALLOC_CHECK; + if (keep_rel_p && relative_path_p) + { + slashify (src_path, posix_path, 0); + debug_printf ("%s = conv_to_posix_path (%s)", posix_path, src_path); + return 0; + } + + char pathbuf[MAX_PATH]; + char cwd[MAX_PATH]; + + /* No need to fetch cwd if path is absolute. */ + if (relative_path_p) + getcwd_inner (cwd, MAX_PATH, 0); /* FIXME: check rc */ + else + strcpy (cwd, "/"); /* some innocuous value */ + MALLOC_CHECK; + int rc = normalize_win32_path (cwd, src_path, pathbuf); + MALLOC_CHECK; + if (rc != 0) + { + debug_printf ("%d = conv_to_posix_path (%s)", rc, src_path); + return rc; + } + nofinalslash (pathbuf, pathbuf); + MALLOC_CHECK; + + int pathbuflen = strlen (pathbuf); + for (int i = 0; i < nmounts; ++i) + { + mount_item &mi = mount[native_sorted[i]]; + if (! path_prefix_p (mi.native_path, pathbuf, mi.native_pathlen)) + continue; + + /* SRC_PATH is in the mount table. */ + int nextchar; + if (!pathbuf[mi.native_pathlen]) + nextchar = 0; + else if (isdirsep (pathbuf[mi.native_pathlen])) + nextchar = -1; + else + nextchar = 1; + + int addslash = nextchar > 0 ? 1 : 0; + if ((mi.posix_pathlen + (pathbuflen - mi.native_pathlen) + addslash) >= MAX_PATH) + return ENAMETOOLONG; + strcpy (posix_path, mi.posix_path); + if (addslash) + strcat (posix_path, "/"); + if (nextchar) + slashify (pathbuf + mi.native_pathlen, + posix_path + addslash + (mi.posix_pathlen == 1 ? 0 : mi.posix_pathlen), + trailing_slash_p); + goto out; + } + + /* Not in the database. This should [theoretically] only happen if either + the path begins with //, or / isn't mounted, or the path has a drive + letter not covered by the mount table. If it's a relative path then the + caller must want an absolute path (otherwise we would have returned + above). So we always return an absolute path at this point. */ + if ((isalpha (pathbuf[0])) && (pathbuf[1] == ':')) + cygdrive_posix_path (pathbuf, posix_path, trailing_slash_p && + pathbuflen > 3); + else + { + /* The use of src_path and not pathbuf here is intentional. + We couldn't translate the path, so just ensure no \'s are present. */ + slashify (src_path, posix_path, trailing_slash_p); + } + +out: + debug_printf ("%s = conv_to_posix_path (%s)", posix_path, src_path); + MALLOC_CHECK; + return 0; +} + +/* Return flags associated with a mount point given the win32 path. */ + +unsigned +mount_info::set_flags_from_win32_path (const char *p) +{ + for (int i = 0; i < nmounts; i++) + { + mount_item &mi = mount[native_sorted[i]]; + if (path_prefix_p (mi.native_path, p, mi.native_pathlen)) + return mi.flags; + } + return 0; +} + +/* read_mounts: Given a specific regkey, read mounts from under its + key. */ + +void +mount_info::read_mounts (reg_key& r) +{ + char posix_path[MAX_PATH]; + HKEY key = r.get_key (); + DWORD i, posix_path_size; + +loop: + for (i = 0; ;i++) + { + posix_path_size = MAX_PATH; + LONG err = RegEnumKeyEx (key, i, posix_path, &posix_path_size, NULL, + NULL, NULL, NULL); + + if (err != ERROR_SUCCESS) + break; + + if (iscygdrive (posix_path)) + { + /* This shouldn't be in the mount table. */ + (void) r.kill (posix_path); + goto loop; + } + } + + /* Loop through subkeys */ + /* FIXME: we would like to not check MAX_MOUNTS but the heap in the + shared area is currently statically allocated so we can't have an + arbitrarily large number of mounts. */ + for (DWORD i = 0; i < MAX_MOUNTS; i++) + { + char native_path[MAX_PATH]; + int mount_flags; + + posix_path_size = MAX_PATH; + /* FIXME: if maximum posix_path_size is 256, we're going to + run into problems if we ever try to store a mount point that's + over 256 but is under MAX_PATH! */ + LONG err = RegEnumKeyEx (key, i, posix_path, &posix_path_size, NULL, + NULL, NULL, NULL); + + if (err == ERROR_NO_MORE_ITEMS) + break; + else if (err != ERROR_SUCCESS) + { + debug_printf ("RegEnumKeyEx failed, error %d!\n", err); + break; + } + + if (iscygdrive (posix_path)) + { + /* This shouldn't be in the mount table. */ + // (void) r.kill (posix_path); + continue; + } + + /* Get a reg_key based on i. */ + reg_key subkey = reg_key (key, KEY_READ, posix_path, NULL); + + /* Check the mount table for prefix matches. */ + for (int j = 0; j < nmounts; j++) + if (strcasematch (mount[j].posix_path, posix_path)) + goto next; /* Can't have more than one */ + + /* Fetch info from the subkey. */ + subkey.get_string ("native", native_path, sizeof (native_path), ""); + mount_flags = subkey.get_int ("flags", 0); + + /* Add mount_item corresponding to registry mount point. */ + cygwin_shared->mount.add_item (native_path, posix_path, mount_flags); + next: + continue; + } +} + +/* from_registry: Build the entire mount table from the registry. Also, + read in cygdrive-related information from its registry location. */ + +void +mount_info::from_registry () +{ + /* Use current mount areas if either user or system mount areas + already exist. Otherwise, import old mounts. */ + + reg_key r; + + /* Retrieve cygdrive-related information. */ + read_cygdrive_info_from_registry (); + + nmounts = 0; + + /* First read mounts from user's table. */ + read_mounts (r); + + /* Then read mounts from system-wide mount table. */ + reg_key r1 (HKEY_LOCAL_MACHINE, KEY_READ, "SOFTWARE", + CYGWIN_INFO_CYGNUS_REGISTRY_NAME, + CYGWIN_INFO_CYGWIN_REGISTRY_NAME, + CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, + NULL); + read_mounts (r1); + + /* If we had to create both user and system mount areas, import + old mounts. */ + if (had_to_create_mount_areas == 2) + import_v1_mounts (); + + sort (); +} + +/* add_reg_mount: Add mount item to registry. Return zero on success, + non-zero on failure. */ +/* FIXME: Need a mutex to avoid collisions with other tasks. */ + +int +mount_info::add_reg_mount (const char * native_path, const char * posix_path, unsigned mountflags) +{ + /* Add the mount to the right registry location, depending on + whether MOUNT_SYSTEM is set in the mount flags. */ + if (!(mountflags & MOUNT_SYSTEM)) /* current_user mount */ + { + /* reg_key for user mounts in HKEY_CURRENT_USER. */ + reg_key reg_user; + + /* Start by deleting existing mount if one exists. */ + reg_user.kill (posix_path); + + /* Create the new mount. */ + reg_key subkey = reg_key (reg_user.get_key (), + KEY_ALL_ACCESS, + posix_path, NULL); + subkey.set_string ("native", native_path); + subkey.set_int ("flags", mountflags); + } + else /* local_machine mount */ + { + /* reg_key for system mounts in HKEY_LOCAL_MACHINE. */ + reg_key reg_sys (HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, "SOFTWARE", + CYGWIN_INFO_CYGNUS_REGISTRY_NAME, + CYGWIN_INFO_CYGWIN_REGISTRY_NAME, + CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, + NULL); + + if (reg_sys.get_key () == INVALID_HANDLE_VALUE) + { + set_errno (EACCES); + return -1; + } + + /* Start by deleting existing mount if one exists. */ + reg_sys.kill (posix_path); + + /* Create the new mount. */ + reg_key subkey = reg_key (reg_sys.get_key (), + KEY_ALL_ACCESS, + posix_path, NULL); + subkey.set_string ("native", native_path); + subkey.set_int ("flags", mountflags); + } + + return 0; /* Success! */ +} + +/* del_reg_mount: delete mount item from registry indicated in flags. + Return zero on success, non-zero on failure.*/ +/* FIXME: Need a mutex to avoid collisions with other tasks. */ + +int +mount_info::del_reg_mount (const char * posix_path, unsigned flags) +{ + int killres; + + if ((flags & MOUNT_SYSTEM) == 0) /* Delete from user registry */ + { + reg_key reg_user (KEY_ALL_ACCESS, + CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, NULL); + killres = reg_user.kill (posix_path); + } + else /* Delete from system registry */ + { + reg_key reg_sys (HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, "SOFTWARE", + CYGWIN_INFO_CYGNUS_REGISTRY_NAME, + CYGWIN_INFO_CYGWIN_REGISTRY_NAME, + CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, + NULL); + + if (reg_sys.get_key () == INVALID_HANDLE_VALUE) + { + set_errno (EACCES); + return -1; + } + + killres = reg_sys.kill (posix_path); + } + + if (killres != ERROR_SUCCESS) + { + __seterrno_from_win_error (killres); + return -1; + } + + return 0; /* Success! */ +} + +/* read_cygdrive_info_from_registry: Read the default prefix and flags + to use when creating cygdrives from the special user registry + location used to store cygdrive information. */ + +void +mount_info::read_cygdrive_info_from_registry () +{ + /* reg_key for user mounts in HKEY_CURRENT_USER. */ + reg_key r; + + if (r.get_string ("cygdrive prefix", cygdrive, sizeof (cygdrive), "") != 0) + { + /* Didn't find it so write the default to the registry and use it. */ + write_cygdrive_info_to_registry ("/cygdrive", MOUNT_AUTO); + } + else + { + /* Fetch cygdrive_flags from registry; returns MOUNT_AUTO on error. */ + cygdrive_flags = r.get_int ("cygdrive flags", MOUNT_AUTO); + slashify (cygdrive, cygdrive, 1); + cygdrive_len = strlen(cygdrive); + } +} + +/* write_cygdrive_info_to_registry: Write the default prefix and flags + to use when creating cygdrives to the special user registry + location used to store cygdrive information. */ + +int +mount_info::write_cygdrive_info_to_registry (const char *cygdrive_prefix, unsigned flags) +{ + /* reg_key for user mounts in HKEY_CURRENT_USER. */ + reg_key r; + + /* Verify cygdrive prefix starts with a forward slash and if there's + another character, it's not a slash. */ + if ((cygdrive_prefix == NULL) || (*cygdrive_prefix == 0) || + (cygdrive_prefix[0] != '/') || + ((cygdrive_prefix[1] != '\0') && (SLASH_P (cygdrive_prefix[1])))) + { + set_errno (EINVAL); + return -1; + } + + char hold_cygdrive_prefix[strlen (cygdrive_prefix) + 1]; + /* Ensure that there is never a final slash */ + nofinalslash (cygdrive_prefix, hold_cygdrive_prefix); + + r.set_string ("cygdrive prefix", hold_cygdrive_prefix); + r.set_int ("cygdrive flags", flags); + + /* This also needs to go in the in-memory copy of "cygdrive" */ + slashify (cygdrive_prefix, cygwin_shared->mount.cygdrive, 1); + cygwin_shared->mount.cygdrive_flags = flags; + cygwin_shared->mount.cygdrive_len = strlen(cygwin_shared->mount.cygdrive); + + return 0; +} + +struct mntent * +mount_info::getmntent (int x) +{ + if (x < 0 || x >= nmounts) + return NULL; + + return mount[native_sorted[x]].getmntent (); +} + +static mount_item *mounts_for_sort; + +/* sort_by_posix_name: qsort callback to sort the mount entries. Sort + user mounts ahead of system mounts to the same POSIX path. */ +/* FIXME: should the user should be able to choose whether to + prefer user or system mounts??? */ +static int +sort_by_posix_name (const void *a, const void *b) +{ + mount_item *ap = mounts_for_sort + (*((int*) a)); + mount_item *bp = mounts_for_sort + (*((int*) b)); + + /* Base weighting on longest posix path first so that the most + obvious path will be chosen. */ + size_t alen = strlen (ap->posix_path); + size_t blen = strlen (bp->posix_path); + + int res = blen - alen; + + if (res) + return res; /* Path lengths differed */ + + /* The two paths were the same length, so just determine normal + lexical sorted order. */ + res = strcmp (ap->posix_path, bp->posix_path); + + if (res == 0) + { + /* need to select between user and system mount to same POSIX path */ + if ((bp->flags & MOUNT_SYSTEM) == 0) /* user mount */ + return 1; + else + return -1; + } + + return res; +} + +/* sort_by_native_name: qsort callback to sort the mount entries. Sort + user mounts ahead of system mounts to the same POSIX path. */ +/* FIXME: should the user should be able to choose whether to + prefer user or system mounts??? */ +static int +sort_by_native_name (const void *a, const void *b) +{ + mount_item *ap = mounts_for_sort + (*((int*) a)); + mount_item *bp = mounts_for_sort + (*((int*) b)); + + /* Base weighting on longest win32 path first so that the most + obvious path will be chosen. */ + size_t alen = strlen (ap->native_path); + size_t blen = strlen (bp->native_path); + + int res = blen - alen; + + if (res) + return res; /* Path lengths differed */ + + /* The two paths were the same length, so just determine normal + lexical sorted order. */ + res = strcasecmp (ap->posix_path, bp->posix_path); + + if (res == 0) + { + /* need to select between user and system mount to same POSIX path */ + if ((bp->flags & MOUNT_SYSTEM) == 0) /* user mount */ + return 1; + else + return -1; + } + + return res; +} + +void +mount_info::sort () +{ + for (int i = 0; i < nmounts; i++) + native_sorted[i] = posix_sorted[i] = i; + /* Sort them into reverse length order, otherwise we won't + be able to look for /foo in /. */ + mounts_for_sort = mount; /* ouch. */ + qsort (posix_sorted, nmounts, sizeof (posix_sorted[0]), sort_by_posix_name); + qsort (native_sorted, nmounts, sizeof (native_sorted[0]), sort_by_native_name); +} + +/* Add an entry to the in-memory mount table. + Returns 0 on success, -1 on failure and errno is set. + + This is where all argument validation is done. It may not make sense to + do this when called internally, but it's cleaner to keep it all here. */ + +int +mount_info::add_item (const char *native, const char *posix, unsigned mountflags) +{ + /* Can't add more than MAX_MOUNTS. */ + if (nmounts == MAX_MOUNTS) + { + set_errno (EMFILE); + return -1; + } + + /* Something's wrong if either path is NULL or empty, or if it's + not a UNC or absolute path. */ + + if ((native == NULL) || (*native == 0) || + (posix == NULL) || (*posix == 0) || + (!slash_unc_prefix_p (native) && !isabspath (native))) + { + set_errno (EINVAL); + return -1; + } + + /* Make sure both paths do not end in /. */ + char nativetmp[MAX_PATH]; + char posixtmp[MAX_PATH]; + + if (slash_drive_prefix_p (native)) + slash_drive_to_win32_path (native, nativetmp, 0); + else + { + backslashify (native, nativetmp, 0); + nofinalslash (nativetmp, nativetmp); + } + + slashify (posix, posixtmp, 0); + nofinalslash (posixtmp, posixtmp); + + debug_printf ("%s[%s], %s[%s], %p", + native, nativetmp, posix, posixtmp, mountflags); + + /* Duplicate /'s in path are an error. */ + for (char *p = posixtmp + 1; *p; ++p) + { + if (p[-1] == '/' && p[0] == '/') + { + set_errno (EINVAL); + return -1; + } + } + + /* Write over an existing mount item with the same POSIX path if + it exists and is from the same registry area. */ + for (int i = 0; i < nmounts; i++) + { + if ((strcmp (mount[i].posix_path, posixtmp) == 0) && + ((mount[i].flags & MOUNT_SYSTEM) == (mountflags & MOUNT_SYSTEM))) + { + /* replace existing mount item */ + mount[i].init (nativetmp, posixtmp, mountflags); + goto sortit; + } + } + + mount[nmounts++].init (nativetmp, posixtmp, mountflags); + +sortit: + sort (); + + return 0; +} + +/* Delete a mount table entry where path is either a Win32 or POSIX + path. Since the mount table is really just a table of aliases, + deleting / is ok (although running without a slash mount is + strongly discouraged because some programs may run erratically + without one). If MOUNT_SYSTEM is set in flags, remove from system + registry, otherwise remove the user registry mount. +*/ + +int +mount_info::del_item (const char *path, unsigned flags) +{ + char pathtmp[MAX_PATH]; + + /* Something's wrong if path is NULL or empty. */ + if ((path == NULL) || (*path == 0)) + { + set_errno (EINVAL); + return -1; + } + + slashify (path, pathtmp, 0); + nofinalslash (pathtmp, pathtmp); + + debug_printf ("%s[%s]", path, pathtmp); + + for (int i = 0; i < nmounts; i++) + { + /* Delete if paths and mount locations match. */ + if (((strcmp (mount[i].posix_path, pathtmp) == 0 + || strcmp (mount[i].native_path, pathtmp) == 0)) && + ((mount[i].flags & MOUNT_SYSTEM) == (flags & MOUNT_SYSTEM))) + { + nmounts--; /* One less mount table entry */ + /* Fill in the hole if not at the end of the table */ + if (i < nmounts) + memcpy (mount + i, mount + i + 1, + sizeof (mount[i]) * (nmounts - i)); + sort (); /* Resort the table */ + return 0; + } + } + set_errno (EINVAL); + return -1; +} + +/* read_v1_mounts: Given a reg_key to an old mount table registry area, + read in the mounts. The "which" arg contains zero if we're reading + the user area and MOUNT_SYSTEM if we're reading the system area. + This way we can store the mounts read in the appropriate place when + they are written back to the new registry layout. */ + +void +mount_info::read_v1_mounts (reg_key r, unsigned which) +{ + unsigned mountflags = 0; + + /* MAX_MOUNTS was 30 when we stopped using the v1 layout */ + for (int i = 0; i < 30; i++) + { + char key_name[10]; + char win32path[MAX_PATH]; + char unixpath[MAX_PATH]; + + __small_sprintf (key_name, "%02x", i); + + reg_key k (r.get_key (), KEY_ALL_ACCESS, key_name, NULL); + + /* The registry names are historical but useful so are left alone. */ + k.get_string ("native", win32path, sizeof (win32path), ""); + k.get_string ("unix", unixpath, sizeof (unixpath), ""); + + /* Does this entry contain something? */ + if (*win32path != 0) + { + mountflags = 0; + + if (k.get_int ("fbinary", 0)) + mountflags |= MOUNT_BINARY; + + /* Or in zero or MOUNT_SYSTEM depending on which table + we're reading. */ + mountflags |= which; + + cygwin_shared->mount.add_item (win32path, unixpath, mountflags); + } + } +} + +/* from_v1_registry: Build the entire mount table from the old v1 registry + mount area. */ + +void +mount_info::from_v1_registry () +{ + reg_key r (HKEY_CURRENT_USER, KEY_ALL_ACCESS, + "SOFTWARE", + "Cygnus Solutions", + "CYGWIN.DLL setup", + "b15.0", + "mounts", + NULL); + + nmounts = 0; + + /* First read mounts from user's table. */ + read_v1_mounts (r, 0); + + /* Then read mounts from system-wide mount table. */ + reg_key r1 (HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, + "SOFTWARE", + "Cygnus Solutions", + "CYGWIN.DLL setup", + "b15.0", + "mounts", + NULL); + read_v1_mounts (r1, MOUNT_SYSTEM); + + /* Note: we don't need to sort internal table here since it is + done in main from_registry call after this function would be + run. */ +} + +/* import_v1_mounts: If v1 mounts are present, load them and write + the new entries to the new registry area. */ + +void +mount_info::import_v1_mounts () +{ + /* Read in old mounts into memory. */ + from_v1_registry (); + + /* Write all mounts to the new registry. */ + to_registry (); +} + +/* to_registry: For every mount point in memory, add a corresponding + registry mount point. */ + +void +mount_info::to_registry () +{ + for (int i = 0; i < MAX_MOUNTS; i++) + { + if (i < nmounts) + { + mount_item *p = mount + i; + + add_reg_mount (p->native_path, p->posix_path, p->flags); + + debug_printf ("%02x: %s, %s, %d", + i, p->native_path, p->posix_path, p->flags); + } + } +} + +/************************* mount_item class ****************************/ + +struct mntent * +mount_item::getmntent () +{ +#ifdef _MT_SAFE + struct mntent &ret=_reent_winsup()->_ret; +#else + static NO_COPY struct mntent ret; +#endif + + /* Pass back pointers to mount_info strings reserved for use by + getmntent rather than pointers to strings in the internal mount + table because the mount table might change, causing weird effects + from the getmntent user's point of view. */ + + strcpy (cygwin_shared->mount.mnt_fsname, native_path); + ret.mnt_fsname = cygwin_shared->mount.mnt_fsname; + strcpy (cygwin_shared->mount.mnt_dir, posix_path); + ret.mnt_dir = cygwin_shared->mount.mnt_dir; + + if (!(flags & MOUNT_SYSTEM)) /* user mount */ + strcpy (cygwin_shared->mount.mnt_type, (char *) "user"); + else /* system mount */ + strcpy (cygwin_shared->mount.mnt_type, (char *) "system"); + + if ((flags & MOUNT_AUTO)) /* cygdrive */ + strcat (cygwin_shared->mount.mnt_type, (char *) ",auto"); + + ret.mnt_type = cygwin_shared->mount.mnt_type; + + /* mnt_opts is a string that details mount params such as + binary or textmode, or exec. We don't print + `silent' here; it's a magic internal thing. */ + + if (! (flags & MOUNT_BINARY)) + strcpy (cygwin_shared->mount.mnt_opts, (char *) "textmode"); + else + strcpy (cygwin_shared->mount.mnt_opts, (char *) "binmode"); + + if (flags & MOUNT_EXEC) + strcat (cygwin_shared->mount.mnt_opts, (char *) ",exec"); + + ret.mnt_opts = cygwin_shared->mount.mnt_opts; + + ret.mnt_freq = 1; + ret.mnt_passno = 1; + return &ret; +} + +/* Fill in the fields of a mount table entry. */ + +void +mount_item::init (const char *native, const char *posix, unsigned mountflags) +{ + strcpy ((char *) native_path, native); + strcpy ((char *) posix_path, posix); + + native_pathlen = strlen (native_path); + posix_pathlen = strlen (posix_path); + + flags = mountflags; +} + +/********************** Mount System Calls **************************/ + +/* Mount table system calls. + Note that these are exported to the application. */ + +/* mount: Add a mount to the mount table in memory and to the registry + that will cause paths under win32_path to be translated to paths + under posix_path. */ + +extern "C" +int +mount (const char *win32_path, const char *posix_path, unsigned flags) +{ + int res = -1; + + if (flags & MOUNT_AUTO) /* normal mount */ + { + /* When flags include MOUNT_AUTO, take this to mean that + we actually want to change the cygdrive prefix and flags + without actually mounting anything. */ + res = cygwin_shared->mount.write_cygdrive_info_to_registry (posix_path, flags); + win32_path = NULL; + } + else + { + if (iscygdrive (posix_path)) + { + set_errno (EINVAL); + return res; /* Don't try to add cygdrive prefix. */ + } + + res = cygwin_shared->mount.add_reg_mount (win32_path, posix_path, flags); + + if (res == 0) + cygwin_shared->mount.add_item (win32_path, posix_path, flags); + } + + syscall_printf ("%d = mount (%s, %s, %p)", res, win32_path, posix_path, flags); + return res; +} + +/* umount: The standard umount call only has a path parameter. Since + it is not possible for this call to specify whether to remove the + mount from the user or global mount registry table, assume the user + table. */ + +extern "C" +int +umount (const char *path) +{ + return cygwin_umount (path, 0); +} + +/* cygwin_umount: This is like umount but takes an additional flags + parameter that specifies whether to umount from the user or system-wide + registry area. */ + +extern "C" +int +cygwin_umount (const char *path, unsigned flags) +{ + int res = cygwin_shared->mount.del_reg_mount (path, flags); + + if (res == 0) + cygwin_shared->mount.del_item (path, flags); + + syscall_printf ("%d = cygwin_umount (%s, %d)", res, path, flags); + return res; +} + +#ifdef _MT_SAFE +#define iteration _reent_winsup()->_iteration +#else +static int iteration; +#endif + +extern "C" +FILE * +setmntent (const char *filep, const char *) +{ + iteration = 0; + return (FILE *) filep; +} + +extern "C" +struct mntent * +getmntent (FILE *) +{ + return cygwin_shared->mount.getmntent (iteration++); +} + +extern "C" +int +endmntent (FILE *) +{ + return 1; +} + +/********************** Symbolic Link Support **************************/ + +/* Create a symlink from FROMPATH to TOPATH. */ + +extern "C" +int +symlink (const char *topath, const char *frompath) +{ + HANDLE h; + int res = -1; + + path_conv win32_path (frompath, SYMLINK_NOFOLLOW); + if (win32_path.error) + { + set_errno (win32_path.error); + goto done; + } + + syscall_printf ("symlink (%s, %s)", topath, win32_path.get_win32 ()); + + if (topath[0] == 0) + { + set_errno (EINVAL); + goto done; + } + if (strlen (topath) >= MAX_PATH) + { + set_errno (ENAMETOOLONG); + goto done; + } + + if (win32_path.is_device () || + win32_path.file_attributes () != (DWORD) -1) + { + set_errno (EEXIST); + goto done; + } + + h = CreateFileA(win32_path.get_win32 (), GENERIC_WRITE, 0, &sec_none_nih, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0); + if (h == INVALID_HANDLE_VALUE) + __seterrno (); + else + { + char buf[sizeof (SYMLINK_COOKIE) + MAX_PATH + 10]; + + __small_sprintf (buf, "%s%s", SYMLINK_COOKIE, topath); + DWORD len = strlen (buf) + 1; + + /* Note that the terminating nul is written. */ + DWORD written; + if (!WriteFile (h, buf, len, &written, NULL) || written != len) + { + __seterrno (); + CloseHandle (h); + DeleteFileA (win32_path.get_win32 ()); + } + else + { + CloseHandle (h); + chmod (frompath, S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO); + res = 0; + } + } + +done: + syscall_printf ("%d = symlink (%s, %s)", res, topath, frompath); + return res; +} + +static __inline char * +has_suffix (const char *path, const suffix_info *suffixes) +{ + char *ext = strrchr (path, '.'); + if (ext) + for (const suffix_info *ex = suffixes; ex->name != NULL; ex++) + if (strcasematch (ext, ex->name)) + return ext; + return NULL; +} + +static int __inline +next_suffix (char *ext_here, const suffix_info *&suffixes) +{ + if (!suffixes) + return 1; + + while (suffixes && suffixes->name) + if (!suffixes->addon) + suffixes++; + else + { + strcpy (ext_here, suffixes->name); + suffixes++; + return 1; + } + return 0; +} + +/* Check if PATH is a symlink. PATH must be a valid Win32 path name. + + If PATH is a symlink, put the value of the symlink--the file to + which it points--into BUF. The value stored in BUF is not + necessarily null terminated. BUFLEN is the length of BUF; only up + to BUFLEN characters will be stored in BUF. BUF may be NULL, in + which case nothing will be stored. + + Set *SYML if PATH is a symlink. + + Set *EXEC if PATH appears to be executable. This is an efficiency + hack because we sometimes have to open the file anyhow. *EXEC will + not be set for every executable file. + + Return -1 on error, 0 if PATH is not a symlink, or the length + stored into BUF if PATH is a symlink. */ + +static int +symlink_check_one (const char *in_path, char *buf, int buflen, DWORD& fileattr, + unsigned *pflags, const suffix_info *suffixes, char *&known_suffix) +{ + HANDLE h; + int res = 0; + char extbuf[buflen + 5]; + char *ext_here; + const char *path = in_path; + + if (!suffixes) + ext_here = NULL; + else if ((known_suffix = has_suffix (in_path, suffixes)) != NULL) + { + suffixes = NULL; + ext_here = NULL; + } + else + { + path = strcpy (extbuf, in_path); + ext_here = strchr (path, '\0'); + } + + *buf = '\0'; + do + { + if (!next_suffix (ext_here, suffixes)) + break; + fileattr = GetFileAttributesA (path); + if (fileattr == (DWORD) -1) + { + /* The GetFileAttributesA call can fail for reasons that don't + matter, so we just return 0. For example, getting the + attributes of \\HOST will typically fail. */ + debug_printf ("GetFileAttributesA (%s) failed", path); + __seterrno (); + continue; + } + + /* Windows allows path\. even when `path' isn't a directory. + Detect this scenario and disallow it, since it is non-UNIX like. */ + char *p = strchr (path, '\0'); + if (p > path + 1 && p[-1] == '.' && SLASH_P (p[-2]) && + !(fileattr & FILE_ATTRIBUTE_DIRECTORY)) + { + debug_printf ("\\. specified on non-directory"); + set_errno (ENOTDIR); + return 0; + } + + /* A symlink will have the `system' file attribute. */ + /* Only files can be symlinks (which can be symlinks to directories). */ + if (!SYMLINKATTR (fileattr)) + goto file_not_symlink; + + /* Check the file's extended attributes, if it has any. */ + int unixattr = 0; + if (fileattr & FILE_ATTRIBUTE_DIRECTORY) + unixattr |= S_IFDIR; + + if (! get_file_attribute (TRUE, path, &unixattr)) + { + if (unixattr & STD_XBITS) + *pflags |= PATH_EXEC; + if (! S_ISLNK (unixattr)) + ; + } + + /* Open the file. */ + + h = CreateFileA (path, GENERIC_READ, FILE_SHARE_READ, &sec_none_nih, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, 0); + + res = -1; + if (h == INVALID_HANDLE_VALUE) + __seterrno (); + else + { + char cookie_buf[sizeof (SYMLINK_COOKIE) - 1]; + DWORD got; + + if (! ReadFile (h, cookie_buf, sizeof (cookie_buf), &got, 0)) + set_errno (EIO); + else if (got == sizeof (cookie_buf) + && memcmp (cookie_buf, SYMLINK_COOKIE, + sizeof (cookie_buf)) == 0) + { + /* It's a symlink. */ + *pflags = PATH_SYMLINK; + + res = ReadFile (h, buf, buflen, &got, 0); + if (!res) + set_errno (EIO); + else + { + /* Versions prior to b16 stored several trailing + NULs with the path (to fill the path out to 1024 + chars). Current versions only store one trailing + NUL. The length returned is the path without + *any* trailing NULs. We also have to handle (or + at least not die from) corrupted paths. */ + if (memchr (buf, 0, got) != NULL) + res = strlen (buf); + else + res = got; + } + } + else if (got == sizeof (cookie_buf) + && memcmp (cookie_buf, SOCKET_COOKIE, + sizeof (cookie_buf)) == 0) + { + res = 0; + *pflags |= PATH_SOCKET; + goto close_and_return; + } + else if (*pflags & PATH_EXEC) + goto close_and_return; + else if (!(*pflags & PATH_EXEC)) + { + /* Not a symlink, see if executable. */ + if (got >= 2 && + ((cookie_buf[0] == '#' && cookie_buf[1] == '!') || + (cookie_buf[0] == ':' && cookie_buf[1] == '\n'))) + *pflags |= PATH_EXEC; + close_and_return: + CloseHandle (h); + goto file_not_symlink; + } + } + CloseHandle (h); + break; + } + while (suffixes); + goto out; + +file_not_symlink: + set_errno (EINVAL); + if (ext_here) + strcpy (buf, ext_here); + res = 0; + +out: + syscall_printf ("%d = symlink_check_one (%s, %p, %d) (%p)", + res, path, buf, buflen, *pflags); + + return res; +} + +/* readlink system call */ + +extern "C" +int +readlink (const char *path, char *buf, int buflen) +{ + path_conv pathbuf (path, SYMLINK_CONTENTS); + if (pathbuf.error) + { + set_errno (pathbuf.error); + syscall_printf ("-1 = readlink (%s, %p, %d)", path, buf, buflen); + return -1; + } + + if (!pathbuf.issymlink ()) + { + if (pathbuf.fileattr != (DWORD) -1) + set_errno (EINVAL); + return -1; + } + + int len = strlen (pathbuf.get_win32 ()); + if (len > (buflen - 1)) + { + set_errno (ENAMETOOLONG); + return -1; + } + memcpy (buf, pathbuf.get_win32 (), len); + buf[len] = '\0'; + + /* errno set by symlink_check_one if error */ + return len; +} + +/* Some programs rely on st_dev/st_ino being unique for each file. + Hash the path name and hope for the best. The hash arg is not + always initialized to zero since readdir needs to compute the + dirent ino_t based on a combination of the hash of the directory + done during the opendir call and the hash or the filename within + the directory. FIXME: Not bullet-proof. */ +/* Cygwin internal */ + +unsigned long __stdcall +hash_path_name (unsigned long hash, const char *name) +{ + if (!*name) + return hash; + + /* Perform some initial permutations on the pathname if this is + not "seeded" */ + if (!hash) + { + /* Simplistic handling of drives. If there is a drive specified, + make sure that the initial letter is upper case. If there is + no \ after the ':' assume access through the root directory + of that drive. + FIXME: Should really honor MS-Windows convention of using + the environment to track current directory on various drives. */ + if (name[1] == ':') + { + char *nn, *newname = (char *) alloca (strlen (name) + 2); + nn = strncpy (newname, name, 2); + if (islower (*nn)) + *newname = toupper (*nn); + *(nn += 2) = '\0'; + name += 2; + if (*name != '\\') + { + *nn = '\\'; + *++nn = '\0'; + } + strcpy (nn, name); + name = newname; + goto hashit; + } + + /* Fill out the hashed path name with the current working directory if + this is not an absolute path and there is no pre-specified hash value. + Otherwise the inodes same will differ depending on whether a file is + referenced with an absolute value or relatively. */ + + if (*name != '\\' && (current_directory_name == NULL || + get_current_directory_name ())) + { + hash = current_directory_hash; + if (name[0] == '.' && name[1] == '\0') + return hash; + hash = hash_path_name (hash, "\\"); + } + } + +hashit: + /* Build up hash. Ignore single trailing slash or \a\b\ != \a\b or + \a\b\. but allow a single \ if that's all there is. */ + do + { + hash += *name + (*name << 17); + hash ^= hash >> 2; + } + while (*++name != '\0' && + !(*name == '\\' && (!name[1] || (name[1] == '.' && !name[2])))); + return hash; +} + +static int +get_current_directory_name () +{ + DWORD dlen, len; + + for (dlen = 256; ; dlen *= 2) + { + current_directory_name = (char *) realloc (current_directory_name, dlen + 2); + if ((len = GetCurrentDirectoryA (dlen, current_directory_name)) < dlen) + break; + } + + if (len == 0) + __seterrno (); + else + current_directory_hash = hash_path_name (0, current_directory_name); + + return len; +} + +/* getcwd */ + +static char * +getcwd_inner (char *buf, size_t ulen, int posix_p) +{ + char *resbuf = NULL; + size_t len = ulen; + + if (current_directory_name == NULL && !get_current_directory_name ()) + return NULL; + + if (!posix_p) + { + if (strlen (current_directory_name) >= len) + set_errno (ERANGE); + else + { + strcpy (buf, current_directory_name); + resbuf = buf; + } + + syscall_printf ("%p (%s) = getcwd_inner (%p, %d, win32) (cached)", + resbuf, resbuf ? resbuf : "", buf, len); + return resbuf; + } + else if (current_directory_posix_name != NULL) + { + if (strlen (current_directory_posix_name) >= len) + set_errno (ERANGE); + else + { + strcpy (buf, current_directory_posix_name); + resbuf = buf; + } + + syscall_printf ("%p (%s) = getcwd_inner (%p, %d, posix) (cached)", + resbuf, resbuf ? resbuf : "", buf, len); + return resbuf; + } + + /* posix_p required and current_directory_posix_name == NULL */ + + char temp[MAX_PATH]; + + /* Turn from Win32 style to our style. */ + cygwin_shared->mount.conv_to_posix_path (current_directory_name, temp, 0); + + size_t tlen = strlen (temp); + + current_directory_posix_name = (char *) realloc ( + current_directory_posix_name, tlen + 1); + if (current_directory_posix_name != NULL) + strcpy (current_directory_posix_name, temp); + + if (tlen >= ulen) + { + /* len was too small */ + set_errno (ERANGE); + } + else + { + strcpy (buf, temp); + resbuf = buf; + } + + syscall_printf ("%p (%s) = getcwd_inner (%p, %d, %s)", + resbuf, resbuf ? resbuf : "", + buf, len, posix_p ? "posix" : "win32"); + return resbuf; +} + +char * +getcwd (char *buf, size_t ulen) +{ + char *res; + + if (buf == NULL || ulen == 0) + { + buf = (char *) alloca (MAX_PATH); + res = getcwd_inner (buf, MAX_PATH, 1); + res = strdup (buf); + } + else + { + res = getcwd_inner (buf, ulen, 1); + } + + return res; +} + +/* getwd: standards? */ +extern "C" +char * +getwd (char *buf) +{ + return getcwd (buf, MAX_PATH); +} + +/* chdir: POSIX 5.2.1.1 */ +extern "C" +int +chdir (const char *dir) +{ + path_conv path (dir); + + if (path.error) + { + set_errno (path.error); + syscall_printf ("-1 = chdir (%s)", dir); + return -1; + } + + char *native_dir = path.get_win32 (); + + /* Check to see if path translates to something like C:. + If it does, append a \ to the native directory specification to + defeat the Windows 95 (i.e. MS-DOS) tendency of returning to + the last directory visited on the given drive. */ + if (isalpha (native_dir[0]) && native_dir[1] == ':' && !native_dir[2]) + { + native_dir[2] = '\\'; + native_dir[3] = '\0'; + } + int res = SetCurrentDirectoryA (native_dir); + if (!res) + __seterrno (); + + /* Clear the cache until we need to retrieve the directory again. */ + if (current_directory_name != NULL) + { + free (current_directory_name); + current_directory_name = NULL; + } + if (current_directory_posix_name != NULL) + { + free (current_directory_posix_name); + current_directory_posix_name = NULL; + } + + syscall_printf ("%d = chdir (%s) (dos %s)", res ? 0 : -1, dir, native_dir); + return res ? 0 : -1; +} + +/******************** Exported Path Routines *********************/ + +/* Cover functions to the path conversion routines. + These are exported to the world as cygwin_foo by cygwin.din. */ + +extern "C" +int +cygwin_conv_to_win32_path (const char *path, char *win32_path) +{ + path_conv p (path, SYMLINK_FOLLOW, 0); + if (p.error) + { + set_errno (p.error); + return -1; + } + + strcpy (win32_path, p.get_win32 ()); + return 0; +} + +extern "C" +int +cygwin_conv_to_full_win32_path (const char *path, char *win32_path) +{ + path_conv p (path, SYMLINK_FOLLOW, 1); + if (p.error) + { + set_errno (p.error); + return -1; + } + + strcpy (win32_path, p.get_win32 ()); + return 0; +} + +/* This is exported to the world as cygwin_foo by cygwin.din. */ + +extern "C" +int +cygwin_conv_to_posix_path (const char *path, char *posix_path) +{ + if (check_null_empty_path_errno (path)) + return -1; + cygwin_shared->mount.conv_to_posix_path (path, posix_path, 1); + return 0; +} + +extern "C" +int +cygwin_conv_to_full_posix_path (const char *path, char *posix_path) +{ + if (check_null_empty_path_errno (path)) + return -1; + cygwin_shared->mount.conv_to_posix_path (path, posix_path, 0); + return 0; +} + +/* The realpath function is supported on some UNIX systems. */ + +extern "C" +char * +realpath (const char *path, char *resolved) +{ + int err; + + path_conv real_path (path, SYMLINK_FOLLOW, 1); + + if (real_path.error) + err = real_path.error; + else + { + err = cygwin_shared->mount.conv_to_posix_path (real_path.get_win32 (), resolved, 0); + if (err == 0) + return resolved; + } + + /* FIXME: on error, we are supposed to put the name of the path + component which could not be resolved into RESOLVED. */ + resolved[0] = '\0'; + + set_errno (err); + return NULL; +} + +/* Return non-zero if path is a POSIX path list. + This is exported to the world as cygwin_foo by cygwin.din. + +DOCTOOL-START +<sect1 id="add-func-cygwin-posix-path-list-p"> + <para>Rather than use a mode to say what the "proper" path list + format is, we allow any, and give apps the tools they need to + convert between the two. If a ';' is present in the path list it's + a Win32 path list. Otherwise, if the first path begins with + [letter]: (in which case it can be the only element since if it + wasn't a ';' would be present) it's a Win32 path list. Otherwise, + it's a POSIX path list.</para> +</sect1> +DOCTOOL-END + */ + +extern "C" +int +cygwin_posix_path_list_p (const char *path) +{ + int posix_p = ! (strchr (path, ';') + || (isalpha (path[0]) && path[1] == ':')); + return posix_p; +} + +/* These are used for apps that need to convert env vars like PATH back and + forth. The conversion is a two step process. First, an upper bound on the + size of the buffer needed is computed. Then the conversion is done. This + allows the caller to use alloca if it wants. */ + +static int +conv_path_list_buf_size (const char *path_list, int to_posix_p) +{ + int i, num_elms, max_mount_path_len, size; + const char *p; + + /* The theory is that an upper bound is + current_size + (num_elms * max_mount_path_len) */ + + char delim = to_posix_p ? ';' : ':'; + p = path_list; + for (num_elms = 1; (p = strchr (p, delim)) != NULL; ++num_elms) + ++p; + + /* 7: strlen ("//c") + slop, a conservative initial value */ + for (max_mount_path_len = 7, i = 0; i < cygwin_shared->mount.nmounts; ++i) + { + int mount_len = (to_posix_p + ? cygwin_shared->mount.mount[i].posix_pathlen + : cygwin_shared->mount.mount[i].native_pathlen); + if (max_mount_path_len < mount_len) + max_mount_path_len = mount_len; + } + + /* 100: slop */ + size = strlen (path_list) + (num_elms * max_mount_path_len) + 100; + return size; +} + +extern "C" +int +cygwin_win32_to_posix_path_list_buf_size (const char *path_list) +{ + return conv_path_list_buf_size (path_list, 1); +} + +extern "C" +int +cygwin_posix_to_win32_path_list_buf_size (const char *path_list) +{ + return conv_path_list_buf_size (path_list, 0); +} + +extern "C" +int +cygwin_win32_to_posix_path_list (const char *win32, char *posix) +{ + conv_path_list (win32, posix, 1); + return 0; +} + +extern "C" +int +cygwin_posix_to_win32_path_list (const char *posix, char *win32) +{ + conv_path_list (posix, win32, 0); + return 0; +} + +/* cygwin_split_path: Split a path into directory and file name parts. + Buffers DIR and FILE are assumed to be big enough. + + Examples (path -> `dir' / `file'): + / -> `/' / `' + "" -> `.' / `' + . -> `.' / `.' (FIXME: should this be `.' / `'?) + .. -> `.' / `..' (FIXME: should this be `..' / `'?) + foo -> `.' / `foo' + foo/bar -> `foo' / `bar' + foo/bar/ -> `foo' / `bar' + /foo -> `/' / `foo' + /foo/bar -> `/foo' / `bar' + c: -> `c:/' / `' + c:/ -> `c:/' / `' + c:foo -> `c:/' / `foo' + c:/foo -> `c:/' / `foo' + */ + +extern "C" +void +cygwin_split_path (const char *path, char *dir, char *file) +{ + int dir_started_p = 0; + + /* Deal with drives. + Remember that c:foo <==> c:/foo. */ + if (isalpha (path[0]) && path[1] == ':') + { + *dir++ = *path++; + *dir++ = *path++; + *dir++ = '/'; + if (! *path) + { + *dir = 0; + *file = 0; + return; + } + if (SLASH_P (*path)) + ++path; + dir_started_p = 1; + } + + /* Determine if there are trailing slashes and "delete" them if present. + We pretend as if they don't exist. */ + const char *end = path + strlen (path); + /* path + 1: keep leading slash. */ + while (end > path + 1 && SLASH_P (end[-1])) + --end; + + /* At this point, END points to one beyond the last character + (with trailing slashes "deleted"). */ + + /* Point LAST_SLASH at the last slash (duh...). */ + const char *last_slash; + for (last_slash = end - 1; last_slash >= path; --last_slash) + if (SLASH_P (*last_slash)) + break; + + if (last_slash == path) + { + *dir++ = '/'; + *dir = 0; + } + else if (last_slash > path) + { + memcpy (dir, path, last_slash - path); + dir[last_slash - path] = 0; + } + else + { + if (dir_started_p) + ; /* nothing to do */ + else + *dir++ = '.'; + *dir = 0; + } + + memcpy (file, last_slash + 1, end - last_slash - 1); + file[end - last_slash - 1] = 0; +} + +/********************** String Helper Functions ************************/ + +#define CHXOR ('a' ^ 'A') +#define ch_case_eq(ch1, ch2) \ + ({ \ + unsigned char x; \ + !((x = ((unsigned char)ch1 ^ (unsigned char)ch2)) && \ + (x != CHXOR || !isalpha (ch1))); \ + }) + +int __stdcall +strncasematch (const char *s1, const char *s2, size_t n) +{ + if (s1 == s2) + return 1; + + n++; + while (--n && *s1) + { + if (!ch_case_eq (*s1, *s2)) + return 0; + s1++; s2++; + } + return !n || *s2 == '\0'; +} + +int __stdcall +strcasematch (const char *s1, const char *s2) +{ + if (s1 == s2) + return 1; + + while (*s1) + { + if (!ch_case_eq (*s1, *s2)) + return 0; + s1++; s2++; + } + return *s2 == '\0'; +} + +char * __stdcall +strcasestr (const char *searchee, const char *lookfor) +{ + if (*searchee == 0) + { + if (*lookfor) + return NULL; + return (char *) searchee; + } + + while (*searchee) + { + int i = 0; + while (1) + { + if (lookfor[i] == 0) + return (char *) searchee; + + if (!ch_case_eq (lookfor[i], searchee[i])) + break; + lookfor++; + } + searchee++; + } + + return NULL; +} |