Fossil

Check-in [9c5bbd6a01]
Login

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

Overview
Comment:Summary: a number of changes to improve windows symlink handling. Detail: fixed file_contains_merge_marker failure on windows symlinks; fixed inadequate S_ISLNK macro for windows; backed out change made to revert query ("OR islink" removed); added special processing to vfile_check_signature for windows symlink type changes; fixed a few flaws in the windows specific posix-compatibility routines to improve symlink handling.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | winsymlink
Files: files | file ages | folders
SHA1: 9c5bbd6a015482d3c456b0cf25ed59049db64cf4
User & Date: sdr 2014-09-25 19:21:45.741
Original Comment: Summary: a number of changes to improve windows symlink handling. Detail: fixed file_contains_merge_market failure on windows symlinks; fixed inadequate S_ISLNK macro for windows; backed out change made to revert query ("OR islink" removed); added special processing to vfile_check_signature for windows symlink type changes; fixed a few flaws in the windows specific posix-compatibility routines to improve symlink handling.
Context
2014-09-26
09:23
Make everything compile on MinGW(-w64). WARNING: will not run on Windows XP, that still has to be fixed! Should work on Vista+. check-in: 631dff61e0 user: jan.nijtmans tags: winsymlink
2014-09-25
19:21
Summary: a number of changes to improve windows symlink handling. Detail: fixed file_contains_merge_marker failure on windows symlinks; fixed inadequate S_ISLNK macro for windows; backed out change made to revert query ("OR islink" removed); added special processing to vfile_check_signature for windows symlink type changes; fixed a few flaws in the windows specific posix-compatibility routines to improve symlink handling. check-in: 9c5bbd6a01 user: sdr tags: winsymlink
05:13
Merge updates from trunk. check-in: c420715158 user: sdr tags: winsymlink
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/checkin.c.
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
                 "OR (pathname>'%q/' %s AND pathname<'%q0' %s)",
                 (blob_size(&where)>0) ? "OR" : "AND", zName,
                 filename_collation(), zName, filename_collation(),
                 zName, filename_collation());
  }

  db_prepare(&q,
    "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)"
    "  FROM vfile "
    " WHERE is_selected(id) %s"
    "   AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1",
    blob_str(&where)
  );
  blob_zero(&rewrittenPathname);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    const char *zDisplayName = zPathname;
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;
    int isRenamed = db_column_int(&q,4);

    char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    if( cwdRelative ){
      file_relative_name(zFullName, &rewrittenPathname, 0);
      zDisplayName = blob_str(&rewrittenPathname);
      if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){
        zDisplayName += 2;  /* no unnecessary ./ prefix */
      }







|













>







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
                 "OR (pathname>'%q/' %s AND pathname<'%q0' %s)",
                 (blob_size(&where)>0) ? "OR" : "AND", zName,
                 filename_collation(), zName, filename_collation(),
                 zName, filename_collation());
  }

  db_prepare(&q,
    "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0), islink"
    "  FROM vfile "
    " WHERE is_selected(id) %s"
    "   AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1",
    blob_str(&where)
  );
  blob_zero(&rewrittenPathname);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    const char *zDisplayName = zPathname;
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;
    int isRenamed = db_column_int(&q,4);
    int isLink = db_column_int(&q,5);
    char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    if( cwdRelative ){
      file_relative_name(zFullName, &rewrittenPathname, 0);
      zDisplayName = blob_str(&rewrittenPathname);
      if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){
        zDisplayName += 2;  /* no unnecessary ./ prefix */
      }
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
        blob_appendf(report, "UPDATED_BY_MERGE %s\n", zDisplayName);
      }else if( isChnged==3 ){
        blob_appendf(report, "ADDED_BY_MERGE %s\n", zDisplayName);
      }else if( isChnged==4 ){
        blob_appendf(report, "UPDATED_BY_INTEGRATE %s\n", zDisplayName);
      }else if( isChnged==5 ){
        blob_appendf(report, "ADDED_BY_INTEGRATE %s\n", zDisplayName);
      }else if( file_contains_merge_marker(zFullName) ){
        blob_appendf(report, "CONFLICT   %s\n", zDisplayName);
      }else{
        blob_appendf(report, "EDITED     %s\n", zDisplayName);
      }
    }else if( isRenamed ){
      blob_appendf(report, "RENAMED    %s\n", zDisplayName);
    }else{







|







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
        blob_appendf(report, "UPDATED_BY_MERGE %s\n", zDisplayName);
      }else if( isChnged==3 ){
        blob_appendf(report, "ADDED_BY_MERGE %s\n", zDisplayName);
      }else if( isChnged==4 ){
        blob_appendf(report, "UPDATED_BY_INTEGRATE %s\n", zDisplayName);
      }else if( isChnged==5 ){
        blob_appendf(report, "ADDED_BY_INTEGRATE %s\n", zDisplayName);
      }else if( !isLink && file_contains_merge_marker(zFullName) ){
        blob_appendf(report, "CONFLICT   %s\n", zDisplayName);
      }else{
        blob_appendf(report, "EDITED     %s\n", zDisplayName);
      }
    }else if( isRenamed ){
      blob_appendf(report, "RENAMED    %s\n", zDisplayName);
    }else{
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0),"
       "       datetime(checkin_mtime(%d,rid),'unixepoch'%s)"
       "  FROM vfile %s"
       " ORDER BY %s", vid, timeline_utc(), blob_str(&where), zOrderBy
    );
  }else{
    db_prepare(&q,
       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
       "  FROM vfile %s"
       " ORDER BY %s", blob_str(&where), zOrderBy
    );
  }
  blob_reset(&where);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isNew = db_column_int(&q,2)==0;
    int chnged = db_column_int(&q,3);
    int renamed = db_column_int(&q,4);

    char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    const char *type = "";
    if( verboseFlag ){
      if( isNew ){
        type = "ADDED      ";
      }else if( isDeleted ){
        type = "DELETED    ";







|











>







328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0),"
       "       datetime(checkin_mtime(%d,rid),'unixepoch'%s)"
       "  FROM vfile %s"
       " ORDER BY %s", vid, timeline_utc(), blob_str(&where), zOrderBy
    );
  }else{
    db_prepare(&q,
       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0), islink"
       "  FROM vfile %s"
       " ORDER BY %s", blob_str(&where), zOrderBy
    );
  }
  blob_reset(&where);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isNew = db_column_int(&q,2)==0;
    int chnged = db_column_int(&q,3);
    int renamed = db_column_int(&q,4);
    int isLink = db_column_int(&q,5);
    char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    const char *type = "";
    if( verboseFlag ){
      if( isNew ){
        type = "ADDED      ";
      }else if( isDeleted ){
        type = "DELETED    ";
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
          type = "UPDATED_BY_MERGE ";
        }else if( chnged==3 ){
          type = "ADDED_BY_MERGE ";
        }else if( chnged==4 ){
          type = "UPDATED_BY_INTEGRATE ";
        }else if( chnged==5 ){
          type = "ADDED_BY_INTEGRATE ";
        }else if( file_contains_merge_marker(zFullName) ){
          type = "CONFLICT   ";
        }else{
          type = "EDITED     ";
        }
      }else if( renamed ){
        type = "RENAMED    ";
      }else{







|







363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
          type = "UPDATED_BY_MERGE ";
        }else if( chnged==3 ){
          type = "ADDED_BY_MERGE ";
        }else if( chnged==4 ){
          type = "UPDATED_BY_INTEGRATE ";
        }else if( chnged==5 ){
          type = "ADDED_BY_INTEGRATE ";
        }else if( !isLink && file_contains_merge_marker(zFullName) ){
          type = "CONFLICT   ";
        }else{
          type = "EDITED     ";
        }
      }else if( renamed ){
        type = "RENAMED    ";
      }else{
Changes to src/file.c.
62
63
64
65
66
67
68

69
70
71




72
73
74


75
76
77
78
79
80
81

#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.
**







>



>
>
>
>



>
>







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

#endif /* INTERFACE */

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

#if defined(_WIN32)
/*
** On Windows S_ISLNK can be true or false.
*/
/* the S_ISLNK provided by dirent.h for windows is inadequate, so fix it */
#if defined(S_ISLNK)
# undef S_ISLNK
#endif
#if !defined(S_ISLNK)
# define S_ISLNK(x) ((x)==S_IFLNK)
#endif
#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.
**
Changes to src/update.c.
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    vid = db_lget_int("checkout", 0);
    vfile_check_signature(vid, 0);
    db_multi_exec(
      "DELETE FROM vmerge;"
      "INSERT OR IGNORE INTO torevert "
      " SELECT pathname"
      "   FROM vfile "
      "  WHERE chnged OR deleted OR rid=0 OR pathname!=origname OR islink;"
    );
  }
  db_multi_exec(
    "INSERT OR IGNORE INTO torevert"
    " SELECT origname"
    "   FROM vfile"
    "  WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);"







|







763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    vid = db_lget_int("checkout", 0);
    vfile_check_signature(vid, 0);
    db_multi_exec(
      "DELETE FROM vmerge;"
      "INSERT OR IGNORE INTO torevert "
      " SELECT pathname"
      "   FROM vfile "
      "  WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
    );
  }
  db_multi_exec(
    "INSERT OR IGNORE INTO torevert"
    " SELECT origname"
    "   FROM vfile"
    "  WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);"
Changes to src/vfile.c.
200
201
202
203
204
205
206







207
208
209
210
211
212
213
    }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){
      if( cksigFlags & CKSIG_ENOTFILE ){
        fossil_warning("not an ordinary file: %s", zName);
        nErr++;
      }
      chnged = 1;
    }







    if( origSize!=currentSize ){
      if( chnged!=1 ){
        /* A file size change is definitive - the file has changed.  No
        ** need to check the mtime or sha1sum */
        chnged = 1;
      }
    }else if( chnged==1 && rid!=0 && !isDeleted ){







>
>
>
>
>
>
>







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
    }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){
      if( cksigFlags & CKSIG_ENOTFILE ){
        fossil_warning("not an ordinary file: %s", zName);
        nErr++;
      }
      chnged = 1;
    }
#if defined(_WIN32)
    if (win32_check_symlink_type_changed(zName)){
      if( chnged!=1 ){
        chnged = 1;
      }
    }else /* make the following if an else if */
#endif
    if( origSize!=currentSize ){
      if( chnged!=1 ){
        /* A file size change is definitive - the file has changed.  No
        ** need to check the mtime or sha1sum */
        chnged = 1;
      }
    }else if( chnged==1 && rid!=0 && !isDeleted ){
Changes to src/winfile.c.
88
89
90
91
92
93
94
95
96
97
98
99




100
101
102
103
104
105
106
      tlen = win32_readlink(tname, tlink, sizeof(tlink));
      fossil_filename_free(tname);
    }
    
    ULARGE_INTEGER ull;

    /* if a link was retrieved, it is a symlink, otherwise a dir or file */
    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;
}







|
|
|

|
>
>
>
>







88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
      tlen = win32_readlink(tname, tlink, sizeof(tlink));
      fossil_filename_free(tname);
    }
    
    ULARGE_INTEGER ull;

    /* if a link was retrieved, it is a symlink, otherwise a dir or file */
    if (tlen == 0){
      buf->st_mode = ((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
                       S_IFDIR : S_IFREG);
    
      buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow;
    }else{
      buf->st_mode = S_IFLNK;
      buf->st_size = tlen;
    }
    
    ull.LowPart = attr.ftLastWriteTime.dwLowDateTime;
    ull.HighPart = attr.ftLastWriteTime.dwHighDateTime;
    buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL;
  }
  return !rc;
}
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    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);







|







130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    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, FILE_FLAG_BACKUP_SEMANTICS, 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);
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  
  /* 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 */
    
      int data_size = sizeof(REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t);
      REPARSE_DATA_BUFFER* data = fossil_malloc(data_size);
      DWORD data_used;







|
|







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
  
  /* 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, FILE_READ_EA, 0, NULL, OPEN_EXISTING, 
      FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);      
    if ((file != NULL) && (file != INVALID_HANDLE_VALUE)){

      /* use DeviceIoControl to get the reparse point data */
    
      int data_size = sizeof(REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t);
      REPARSE_DATA_BUFFER* data = fossil_malloc(data_size);
      DWORD data_used;
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
** files and for directories. Thus you must unlink a file symlink or rmdir a
** directory symlink. This is a convenience function used when we know we're
** deleting a symlink of some type.
** Returns 0 on success, 1 on failure.
*/
int win32_unlink_rmdir(const wchar_t *zFilename){
  int rc = 0;
  fossilStat stat;
  if (win32_stat(zFilename, &stat) == 0){
    if (stat.st_mode == S_IFDIR)
      rc = RemoveDirectoryW(zFilename);
    else
      rc = DeleteFileW(zFilename);
  }
  return !rc;
}








|
|
|







225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
** files and for directories. Thus you must unlink a file symlink or rmdir a
** directory symlink. This is a convenience function used when we know we're
** deleting a symlink of some type.
** Returns 0 on success, 1 on failure.
*/
int win32_unlink_rmdir(const wchar_t *zFilename){
  int rc = 0;
  WIN32_FILE_ATTRIBUTE_DATA attr;
  if (GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr)){
    if ((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
      rc = RemoveDirectoryW(zFilename);
    else
      rc = DeleteFileW(zFilename);
  }
  return !rc;
}

272
273
274
275
276
277
278
























279
280
281
282
283
284
285
    blob_write_to_file(&content, newpath);
    blob_reset(&content);
    created = 1;
  }
  
  return !created;
}

























/*
** Check if symlinks are potentially supported on the current OS for the given file.
** Theoretically this code should work on any NT based version of windows
** but I have no way of testing that. The initial check for
** IsWindowsVistaOrGreater() should in theory eliminate any system prior to
** Windows Vista, but I have no way to test that at this time.







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







276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    blob_write_to_file(&content, newpath);
    blob_reset(&content);
    created = 1;
  }
  
  return !created;
}

/*
** Given a pathname to a file, return true if:
**   1. the file exists
**   2. the file is a symbolic link
**   3. the symbolic link's attributes can be acquired
**   4. the symbolic link type is different than the target type
*/
int win32_check_symlink_type_changed(const char* zName){
  int changed = 0;
  wchar_t* zMbcs;
  fossilStat lstat_buf, stat_buf;
  WIN32_FILE_ATTRIBUTE_DATA lstat_attr;
  zMbcs = fossil_utf8_to_filename(zName);
  if (win32_stat(zMbcs, &stat_buf) != 0)
    stat_buf.st_mode = S_IFREG;
  changed =
    (win32_lstat(zMbcs, &lstat_buf) == 0) &&
    (lstat_buf.st_mode == S_IFLNK) &&
    GetFileAttributesExW(zMbcs, GetFileExInfoStandard, &lstat_attr) &&
    ((stat_buf.st_mode == S_IFDIR) != ((lstat_attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY));
  fossil_filename_free(zMbcs);
  return changed;
}

/*
** Check if symlinks are potentially supported on the current OS for the given file.
** Theoretically this code should work on any NT based version of windows
** but I have no way of testing that. The initial check for
** IsWindowsVistaOrGreater() should in theory eliminate any system prior to
** Windows Vista, but I have no way to test that at this time.