Move computation of absolute paths from Makefile to runtime (in preparation for RUNTIME_PREFIX)
[gitweb.git] / compat / mingw.c
index 1ef2a4caf2be92acebd847a9707c78e41d26eb50..3dbe6a77ffa4675b19e7183fd49e11212cb2cda0 100644 (file)
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "win32.h"
 #include "../strbuf.h"
 
 unsigned int _CRT_fmode = _O_BINARY;
@@ -23,6 +24,129 @@ int mingw_open (const char *filename, int oflags, ...)
        return fd;
 }
 
+static inline time_t filetime_to_time_t(const FILETIME *ft)
+{
+       long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
+       winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
+       winTime /= 10000000;             /* Nano to seconds resolution */
+       return (time_t)winTime;
+}
+
+/* We keep the do_lstat code in a separate function to avoid recursion.
+ * When a path ends with a slash, the stat will fail with ENOENT. In
+ * this case, we strip the trailing slashes and stat again.
+ */
+static int do_lstat(const char *file_name, struct stat *buf)
+{
+       WIN32_FILE_ATTRIBUTE_DATA fdata;
+
+       if (!(errno = get_file_attr(file_name, &fdata))) {
+               buf->st_ino = 0;
+               buf->st_gid = 0;
+               buf->st_uid = 0;
+               buf->st_nlink = 1;
+               buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_dev = buf->st_rdev = 0; /* not used by Git */
+               buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
+               buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
+               buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+               return 0;
+       }
+       return -1;
+}
+
+/* We provide our own lstat/fstat functions, since the provided
+ * lstat/fstat functions are so slow. These stat functions are
+ * tailored for Git's usage (read: fast), and are not meant to be
+ * complete. Note that Git stat()s are redirected to mingw_lstat()
+ * too, since Windows doesn't really handle symlinks that well.
+ */
+int mingw_lstat(const char *file_name, struct stat *buf)
+{
+       int namelen;
+       static char alt_name[PATH_MAX];
+
+       if (!do_lstat(file_name, buf))
+               return 0;
+
+       /* if file_name ended in a '/', Windows returned ENOENT;
+        * try again without trailing slashes
+        */
+       if (errno != ENOENT)
+               return -1;
+
+       namelen = strlen(file_name);
+       if (namelen && file_name[namelen-1] != '/')
+               return -1;
+       while (namelen && file_name[namelen-1] == '/')
+               --namelen;
+       if (!namelen || namelen >= PATH_MAX)
+               return -1;
+
+       memcpy(alt_name, file_name, namelen);
+       alt_name[namelen] = 0;
+       return do_lstat(alt_name, buf);
+}
+
+#undef fstat
+int mingw_fstat(int fd, struct stat *buf)
+{
+       HANDLE fh = (HANDLE)_get_osfhandle(fd);
+       BY_HANDLE_FILE_INFORMATION fdata;
+
+       if (fh == INVALID_HANDLE_VALUE) {
+               errno = EBADF;
+               return -1;
+       }
+       /* direct non-file handles to MS's fstat() */
+       if (GetFileType(fh) != FILE_TYPE_DISK)
+               return fstat(fd, buf);
+
+       if (GetFileInformationByHandle(fh, &fdata)) {
+               buf->st_ino = 0;
+               buf->st_gid = 0;
+               buf->st_uid = 0;
+               buf->st_nlink = 1;
+               buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_dev = buf->st_rdev = 0; /* not used by Git */
+               buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
+               buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
+               buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+               return 0;
+       }
+       errno = EBADF;
+       return -1;
+}
+
+static inline void time_t_to_filetime(time_t t, FILETIME *ft)
+{
+       long long winTime = t * 10000000LL + 116444736000000000LL;
+       ft->dwLowDateTime = winTime;
+       ft->dwHighDateTime = winTime >> 32;
+}
+
+int mingw_utime (const char *file_name, const struct utimbuf *times)
+{
+       FILETIME mft, aft;
+       int fh, rc;
+
+       /* must have write permission */
+       if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0)
+               return -1;
+
+       time_t_to_filetime(times->modtime, &mft);
+       time_t_to_filetime(times->actime, &aft);
+       if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) {
+               errno = EINVAL;
+               rc = -1;
+       } else
+               rc = 0;
+       close(fh);
+       return rc;
+}
+
 unsigned int sleep (unsigned int seconds)
 {
        Sleep(seconds*1000);
@@ -104,8 +228,13 @@ int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
 {
        int i, pending;
 
-       if (timeout != -1)
+       if (timeout >= 0) {
+               if (nfds == 0) {
+                       Sleep(timeout);
+                       return 0;
+               }
                return errno = EINVAL, error("poll timeout not supported");
+       }
 
        /* When there is only one fd to wait for, then we pretend that
         * input is available and let the actual wait happen when the
@@ -187,6 +316,19 @@ char *mingw_getcwd(char *pointer, int len)
        return ret;
 }
 
+#undef getenv
+char *mingw_getenv(const char *name)
+{
+       char *result = getenv(name);
+       if (!result && !strcmp(name, "TMPDIR")) {
+               /* on Windows it is TMP and TEMP */
+               result = getenv("TMP");
+               if (!result)
+                       result = getenv("TEMP");
+       }
+       return result;
+}
+
 /*
  * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
  * (Parsing C++ Command-Line Arguments)
@@ -344,7 +486,8 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on
                return xstrdup(path);
        path[strlen(path)-4] = '\0';
        if ((!exe_only || isexe) && access(path, F_OK) == 0)
-               return xstrdup(path);
+               if (!(GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY))
+                       return xstrdup(path);
        return NULL;
 }
 
@@ -393,12 +536,16 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
                 * would normally create a console window. But
                 * since we'll be redirecting std streams, we do
                 * not need the console.
+                * It is necessary to use DETACHED_PROCESS
+                * instead of CREATE_NO_WINDOW to make ssh
+                * recognize that it has no console.
                 */
-               flags = CREATE_NO_WINDOW;
+               flags = DETACHED_PROCESS;
        } else {
                /* There is already a console. If we specified
-                * CREATE_NO_WINDOW here, too, Windows would
+                * DETACHED_PROCESS here, too, Windows would
                 * disassociate the child from the console.
+                * The same is true for CREATE_NO_WINDOW.
                 * Go figure!
                 */
                flags = 0;
@@ -672,6 +819,8 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz)
 #undef rename
 int mingw_rename(const char *pold, const char *pnew)
 {
+       DWORD attrs;
+
        /*
         * Try native rename() first to get errno right.
         * It is based on MoveFile(), which cannot overwrite existing files.
@@ -683,12 +832,19 @@ int mingw_rename(const char *pold, const char *pnew)
        if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
                return 0;
        /* TODO: translate more errors */
-       if (GetLastError() == ERROR_ACCESS_DENIED) {
-               DWORD attrs = GetFileAttributes(pnew);
-               if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+       if (GetLastError() == ERROR_ACCESS_DENIED &&
+           (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) {
+               if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
                        errno = EISDIR;
                        return -1;
                }
+               if ((attrs & FILE_ATTRIBUTE_READONLY) &&
+                   SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
+                       if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
+                               return 0;
+                       /* revert file attributes on failure */
+                       SetFileAttributes(pnew, attrs);
+               }
        }
        errno = EACCES;
        return -1;
@@ -825,3 +981,25 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler)
        timer_fn = handler;
        return old;
 }
+
+static const char *make_backslash_path(const char *path)
+{
+       static char buf[PATH_MAX + 1];
+       char *c;
+
+       if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+               die("Too long path: %.*s", 60, path);
+
+       for (c = buf; *c; c++) {
+               if (*c == '/')
+                       *c = '\\';
+       }
+       return buf;
+}
+
+void mingw_open_html(const char *unixpath)
+{
+       const char *htmlpath = make_backslash_path(unixpath);
+       printf("Launching default browser to display HTML ...\n");
+       ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+}