Fossil

Check-in [de8035cca6]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Work in progress for windows symlink support.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | winsymlink
Files: files | file ages | folders
SHA1: de8035cca6ebd69c783bcf42d46447623910a964
User & Date: sdr 2014-09-15 01:49:41.591
Original User & Date: scott 2014-09-15 01:49:41.591
Context
2014-09-23
00:15
Cherrypicked a number of commits from a "bad branch" (I somehow fouled up a merge from trunk). check-in: 2d75e87b76 user: sdr tags: winsymlink
2014-09-20
15:28
Merged updates from trunk. check-in: e2c5960617 user: sdr tags: bad-winsymlink
2014-09-15
01:49
Work in progress for windows symlink support. check-in: de8035cca6 user: sdr tags: winsymlink
2014-09-13
16:04
Make "fossil ui" on Windows aware of current checkout. Allows using special names prev/next/current in ui check-in: f62bedf1ef user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/blob.c.
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760

761
762
763
764







765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
  if( got<size ){
    blob_resize(pBlob, got);
  }
  return got;
}

/*
** Reads symlink destination path and puts int into blob.
** Any prior content of the blob is discarded, not freed.
**
** Returns length of destination path.
**
** On windows, zeros blob and returns 0.
*/
int blob_read_link(Blob *pBlob, const char *zFilename){
#if !defined(_WIN32)
  char zBuf[1024];

  ssize_t len = readlink(zFilename, zBuf, 1023);
  if( len < 0 ){
    fossil_fatal("cannot read symbolic link %s", zFilename);
  }







  zBuf[len] = 0;   /* null-terminate */
  blob_zero(pBlob);
  blob_appendf(pBlob, "%s", zBuf);
  return len;
#else
  blob_zero(pBlob);
  return 0;
#endif
}


/*
** Write the content of a blob into a file.
**
** If the filename is blank or "-" then write to standard output.







|




|


<

>




>
>
>
>
>
>
>




<
<
<
<







744
745
746
747
748
749
750
751
752
753
754
755
756
757
758

759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775




776
777
778
779
780
781
782
  if( got<size ){
    blob_resize(pBlob, got);
  }
  return got;
}

/*
** Reads symlink destination path and puts it into blob.
** Any prior content of the blob is discarded, not freed.
**
** Returns length of destination path.
**
** On windows, zeros blob and returns 0 if symlinks are not supported.
*/
int blob_read_link(Blob *pBlob, const char *zFilename){

  char zBuf[1024];
#if !defined(_WIN32)
  ssize_t len = readlink(zFilename, zBuf, 1023);
  if( len < 0 ){
    fossil_fatal("cannot read symbolic link %s", zFilename);
  }
#else
  ssize_t len = win32_readlink(zFilename, zBuf, 1023);
  if( len < 0 ){
    blob_zero(pBlob);
    return 0;
  }
#endif
  zBuf[len] = 0;   /* null-terminate */
  blob_zero(pBlob);
  blob_appendf(pBlob, "%s", zBuf);
  return len;




}


/*
** Write the content of a blob into a file.
**
** If the filename is blank or "-" then write to standard output.
Changes to src/checkin.c.
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
    int isSelected = db_column_int(&q, 6);
    const char *zPerm;
    int cmp;

    blob_resize(&filename, nBasename);
    blob_append(&filename, zName, -1);

#if !defined(_WIN32)
    /* For unix, extract the "executable" and "symlink" permissions
    ** directly from the filesystem.  On windows, permissions are
    ** unchanged from the original.  However, only do this if the file
    ** itself is actually selected to be part of this check-in.
    */
    if( isSelected ){
      int mPerm;

      mPerm = file_wd_perm(blob_str(&filename));
      isExe = ( mPerm==PERM_EXE );
      isLink = ( mPerm==PERM_LNK );
    }
#endif
    if( isExe ){
      zPerm = " x";
    }else if( isLink ){
      zPerm = " l"; /* note: symlinks don't have executable bit on unix */
    }else{
      zPerm = "";
    }







<
|
|
<









|







1104
1105
1106
1107
1108
1109
1110

1111
1112

1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
    int isSelected = db_column_int(&q, 6);
    const char *zPerm;
    int cmp;

    blob_resize(&filename, nBasename);
    blob_append(&filename, zName, -1);


    /* Extract the "executable" and "symlink" permissions
    ** directly from the filesystem.  However, only do this if the file

    ** itself is actually selected to be part of this check-in.
    */
    if( isSelected ){
      int mPerm;

      mPerm = file_wd_perm(blob_str(&filename));
      isExe = ( mPerm==PERM_EXE );
      isLink = ( mPerm==PERM_LNK );
    }

    if( isExe ){
      zPerm = " x";
    }else if( isLink ){
      zPerm = " l"; /* note: symlinks don't have executable bit on unix */
    }else{
      zPerm = "";
    }
Changes to src/file.c.
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92



93

94
95
96
97
98
99
100
#endif /* INTERFACE */

#if !defined(_WIN32) || !(defined(__MSVCRT__) || defined(_MSC_VER))
# define fossilStat stat
#endif

/*
** On Windows S_ISLNK always returns FALSE.
*/
#if !defined(S_ISLNK)
# define S_ISLNK(x) (0)
#endif
static int fileStatValid = 0;
static struct fossilStat fileStat;

/*
** Fill stat buf with information received from stat() or lstat().
** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on.
**
*/
static int fossil_stat(const char *zFilename, struct fossilStat *buf, int isWd){
  int rc;
  void *zMbcs = fossil_utf8_to_filename(zFilename);
#if !defined(_WIN32)
  if( isWd && g.allowSymlinks ){
    rc = lstat(zMbcs, buf);
  }else{
    rc = stat(zMbcs, buf);
  }
#else



  rc = win32_stat(zMbcs, buf, isWd);

#endif
  fossil_filename_free(zMbcs);
  return rc;
}

/*
** Fill in the fileStat variable for the file named zFilename.







|


|



















>
>
>
|
>







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#endif /* INTERFACE */

#if !defined(_WIN32) || !(defined(__MSVCRT__) || defined(_MSC_VER))
# define fossilStat stat
#endif

/*
** On Windows S_ISLNK can be true or false.
*/
#if !defined(S_ISLNK)
# define S_ISLNK(x) ((x)==S_IFLNK)
#endif
static int fileStatValid = 0;
static struct fossilStat fileStat;

/*
** Fill stat buf with information received from stat() or lstat().
** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on.
**
*/
static int fossil_stat(const char *zFilename, struct fossilStat *buf, int isWd){
  int rc;
  void *zMbcs = fossil_utf8_to_filename(zFilename);
#if !defined(_WIN32)
  if( isWd && g.allowSymlinks ){
    rc = lstat(zMbcs, buf);
  }else{
    rc = stat(zMbcs, buf);
  }
#else
  if( isWd && g.allowSymlinks ){
    rc = win32_lstat(zMbcs, buf);
  }else{
    rc = win32_stat(zMbcs, buf);
  }
#endif
  fossil_filename_free(zMbcs);
  return rc;
}

/*
** Fill in the fileStat variable for the file named zFilename.
182
183
184
185
186
187
188




189
190
191
192
193
194
195
196
** Create symlink to file on Unix, or plain-text file with
** symlink target if "allow-symlinks" is off or we're on Windows.
**
** Arguments: target file (symlink will point to it), link file
**/
void symlink_create(const char *zTargetFile, const char *zLinkFile){
#if !defined(_WIN32)




  if( g.allowSymlinks ){
    int i, nName;
    char *zName, zBuf[1000];

    nName = strlen(zLinkFile);
    if( nName>=sizeof(zBuf) ){
      zName = mprintf("%s", zLinkFile);
    }else{







>
>
>
>
|







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
** Create symlink to file on Unix, or plain-text file with
** symlink target if "allow-symlinks" is off or we're on Windows.
**
** Arguments: target file (symlink will point to it), link file
**/
void symlink_create(const char *zTargetFile, const char *zLinkFile){
#if !defined(_WIN32)
  int symlinks_supported = 1;
#else
  int symlinks_supported = win32_symlinks_supported(zLinkFile);
#endif
  if( symlinks_supported && g.allowSymlinks ){
    int i, nName;
    char *zName, zBuf[1000];

    nName = strlen(zLinkFile);
    if( nName>=sizeof(zBuf) ){
      zName = mprintf("%s", zLinkFile);
    }else{
204
205
206
207
208
209
210

211




212
213
214
215
216
217
218
219
220
221
222
223
224
          if( file_mkdir(zName, 1) ){
            fossil_fatal_recursive("unable to create directory %s", zName);
            return;
          }
        zName[i] = '/';
      }
    }

    if( symlink(zTargetFile, zName)!=0 ){




      fossil_fatal_recursive("unable to create symlink \"%s\"", zName);
    }
    if( zName!=zBuf ) free(zName);
  }else
#endif
  {
    Blob content;
    blob_set(&content, zTargetFile);
    blob_write_to_file(&content, zLinkFile);
    blob_reset(&content);
  }
}








>
|
>
>
>
>



|
<
<







212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228


229
230
231
232
233
234
235
          if( file_mkdir(zName, 1) ){
            fossil_fatal_recursive("unable to create directory %s", zName);
            return;
          }
        zName[i] = '/';
      }
    }
#if !defined(_WIN32)
    if( symlink(zTargetFile, zName)!=0 )
#else
    if( win32_symlink(zTargetFile, zName)!=0 )
#endif
    {
      fossil_fatal_recursive("unable to create symlink \"%s\"", zName);
    }
    if( zName!=zBuf ) free(zName);
  }else{


    Blob content;
    blob_set(&content, zTargetFile);
    blob_write_to_file(&content, zLinkFile);
    blob_reset(&content);
  }
}

241
242
243
244
245
246
247
248
249
250
251
252
253

254
255
256
257
258
259
260
261
262
263
264
265
266
int file_wd_perm(const char *zFilename){
  if( getStat(zFilename, 1) ) return PERM_REG;
#if defined(_WIN32)
#  ifndef S_IXUSR
#    define S_IXUSR  _S_IEXEC
#  endif
  if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
    return PERM_EXE;
  else
    return PERM_REG;
#else
  if( S_ISREG(fileStat.st_mode) &&
      ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 )

    return PERM_EXE;
  else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
    return PERM_LNK;
  else
    return PERM_REG;
#endif
}

/*
** Return TRUE if the named file is an executable.  Return false
** for directories, devices, fifos, symlinks, etc.
*/
int file_wd_isexe(const char *zFilename){







<
<
<



>





<







252
253
254
255
256
257
258



259
260
261
262
263
264
265
266
267

268
269
270
271
272
273
274
int file_wd_perm(const char *zFilename){
  if( getStat(zFilename, 1) ) return PERM_REG;
#if defined(_WIN32)
#  ifndef S_IXUSR
#    define S_IXUSR  _S_IEXEC
#  endif
  if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )



#else
  if( S_ISREG(fileStat.st_mode) &&
      ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 )
#endif
    return PERM_EXE;
  else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
    return PERM_LNK;
  else
    return PERM_REG;

}

/*
** Return TRUE if the named file is an executable.  Return false
** for directories, devices, fifos, symlinks, etc.
*/
int file_wd_isexe(const char *zFilename){
Changes to src/winfile.c.
19
20
21
22
23
24
25

26
27
28
29
30



















































31
32
33
34
35
36
37









































38
39

40




41

42





43



44













































45








46


47





48


49






























50
51
52
53
54
55
56
** on Windows using the Win32 API.
*/
#include "config.h"
#ifdef _WIN32
/* This code is for win32 only */
#include <sys/stat.h>
#include <windows.h>

#include "winfile.h"

#ifndef LABEL_SECURITY_INFORMATION
#   define LABEL_SECURITY_INFORMATION (0x00000010L)
#endif




















































/*
** Fill stat buf with information received from stat() or lstat().
** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on.
**
*/
int win32_stat(const wchar_t *zFilename, struct fossilStat *buf, int isWd){









































  WIN32_FILE_ATTRIBUTE_DATA attr;
  int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr);

  if( rc ){




    ULARGE_INTEGER ull;

    ull.LowPart = attr.ftLastWriteTime.dwLowDateTime;





    ull.HighPart = attr.ftLastWriteTime.dwHighDateTime;



    buf->st_mode = (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?













































                   S_IFDIR : S_IFREG;








    buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow;


    buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL;





  }


  return !rc;






























}

/*
** Wrapper around the access() system call.  This code was copied from Tcl
** 8.6 and then modified.
*/
int win32_access(const wchar_t *zFilename, int flags){







>





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
>
|
>
>
>
>
|
>
|
>
>
>
>
>
|
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
** on Windows using the Win32 API.
*/
#include "config.h"
#ifdef _WIN32
/* This code is for win32 only */
#include <sys/stat.h>
#include <windows.h>
#include <versionhelpers.h>
#include "winfile.h"

#ifndef LABEL_SECURITY_INFORMATION
#   define LABEL_SECURITY_INFORMATION (0x00000010L)
#endif

/* copy & paste from ntifs.h */
typedef struct _REPARSE_DATA_BUFFER {
  ULONG  ReparseTag;
  USHORT ReparseDataLength;
  USHORT Reserved;
  union {
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      ULONG  Flags;
      WCHAR  PathBuffer[1];
    } SymbolicLinkReparseBuffer;
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      WCHAR  PathBuffer[1];
    } MountPointReparseBuffer;
    struct {
      UCHAR DataBuffer[1];
    } GenericReparseBuffer;
  };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;

#define LINK_BUFFER_SIZE 1024

int win32_lstat(const wchar_t *zFilename, struct fossilStat *buf){
  WIN32_FILE_ATTRIBUTE_DATA attr;
  int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr);
  if( rc ){
    char *tname = fossil_filename_to_utf8(zFilename);
    char tlink[LINK_BUFFER_SIZE];
    ssize_t tlen = win32_readlink(tname, tlink, sizeof(tlink));
    ULARGE_INTEGER ull;

    buf->st_mode = (tlen > 0) ? S_IFLNK :
                   ((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
                     S_IFDIR : S_IFREG);
    
    buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow;
    
    ull.LowPart = attr.ftLastWriteTime.dwLowDateTime;
    ull.HighPart = attr.ftLastWriteTime.dwHighDateTime;
    buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL;
  }
  return !rc;
}

/*
** Fill stat buf with information received from stat() or lstat().
** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on.
**
*/
int win32_stat(const wchar_t *zFilename, struct fossilStat *buf){
  int rc;
  HANDLE file;
  wchar_t nextFilename[LINK_BUFFER_SIZE];
  DWORD len;
  
  while (1){
    rc = win32_lstat(zFilename, buf);
    /* exit on error or not link */
    if ((rc != 0) || (buf->st_mode != S_IFLNK))
      break;

    /* it is a link, so open the linked file */      
    file = CreateFileW(zFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    if ((file == NULL) || (file == INVALID_HANDLE_VALUE)){
      rc = -1;
      break;
    }

    /* get the final path name and close the handle */
    len = GetFinalPathNameByHandleW(file, nextFilename, LINK_BUFFER_SIZE - 1, 0);
    CloseHandle(file);
    
    /* if any problems getting the final path name error so exit */
    if ((len <= 0) || (len > LINK_BUFFER_SIZE - 1)){
      rc = -1;
      break;
    }
    
    /* prepare to try again just in case we have a chain to follow */
    /* this shouldn't happen, but just trying to be safe */
    zFilename = nextFilename;
  }
  
  return rc;
}

ssize_t win32_readlink(const char *path, char *buf, size_t bufsiz){
  /* assume we're going to fail */
  ssize_t rv = -1;
  
  /* does path reference a reparse point? */
  WIN32_FILE_ATTRIBUTE_DATA attr;
  int rc = GetFileAttributesEx(path, GetFileExInfoStandard, &attr);
  if (rc && (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)){
  
    /* since it is a reparse point, open it */
    HANDLE file = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 
      FILE_FLAG_OPEN_REPARSE_POINT, NULL);      
    if ((file != NULL) && (file != INVALID_HANDLE_VALUE)){

      /* use DeviceIoControl to get the reparse point data */
    
      union {
        REPARSE_DATA_BUFFER data;
        char buffer[sizeof(REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t)];
      } u;
      DWORD bytes;
      
      u.data.ReparseTag = IO_REPARSE_TAG_SYMLINK;
      u.data.ReparseDataLength = 0;
      u.data.Reserved = 0;
    
      int rc = DeviceIoControl(file, FSCTL_GET_REPARSE_POINT, NULL, 0,
        &u, sizeof(u), &bytes, NULL);

      /* did the reparse point data fit into the desired buffer? */
      if (rc && (bytes < sizeof(u))){
        /* it fit, so setup the print name for further processing */
        USHORT
          offset = u.data.SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t),
          length = u.data.SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t);
        char *temp;
        u.data.SymbolicLinkReparseBuffer.PathBuffer[offset + length] = 0;

        /* convert the filename to utf8, copy it, and discard the converted copy */
        temp = fossil_filename_to_utf8(u.data.SymbolicLinkReparseBuffer.PathBuffer + offset);
        rv = strlen(temp);
        if (rv >= bufsiz)
          rv = bufsiz;
        memcpy(buf, temp, rv);
        fossil_filename_free(temp);
      }
      
      /* all done, close the reparse point */
      CloseHandle(file);
    }
  }

  return rv;
}

int win32_symlink(const char *oldpath, const char *newpath){
  fossilStat stat;
  int created = 0;
  DWORD flags = 0;
  wchar_t *zMbcs;

  /* does oldpath exist? is it a dir or a file? */  
  zMbcs = fossil_utf8_to_filename(oldpath);
  if (win32_stat(zMbcs, &stat) == 0){
    if (stat.st_mode == S_IFDIR)
      flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
    DeleteFile(newpath);
    if (CreateSymbolicLink(newpath, oldpath, flags))
      created = 1;
  }
  fossil_filename_free(zMbcs);

  /* if the symlink was not created, create a plain text file */
  if (!created){
    Blob content;
    blob_set(&content, oldpath);
    blob_write_to_file(&content, newpath);
    blob_reset(&content);
    created = 1;
  }
  
  return created ? 0 : -1;
}

int win32_symlinks_supported(){
  TOKEN_PRIVILEGES tp;
  LUID luid;
  HANDLE process, token;
  DWORD status;

  /* symlinks only supported on vista or greater */
  if (!IsWindowsVistaOrGreater())
    return 0;
  
  /* next we need to check to see if the privilege is available */
  
  /* can't check privilege if we can't lookup its value */
  if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid))
    return 0;
  
  /* can't check privilege if we can't open the process token */
  process = GetCurrentProcess();
  if (!OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
    return 0;
  
  /* by this point, we have a process token and the privilege value */
  /* try to enable the privilege then close the token */
  
  tp.PrivilegeCount = 1;
  tp.Privileges[0].Luid = luid;
  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  
  AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
  status = GetLastError();
  
  CloseHandle(token);

  /* any error means we failed to enable the privilege, symlinks not supported */
  if (status != ERROR_SUCCESS)
    return 0;
  
  /* we made it this far, symlinks must be supported */
  return 1;  
}

/*
** Wrapper around the access() system call.  This code was copied from Tcl
** 8.6 and then modified.
*/
int win32_access(const wchar_t *zFilename, int flags){