source: trunk/libtransmission/file-win32.c @ 14314

Last change on this file since 14314 was 14314, checked in by jordan, 8 years ago

(trunk, libT) mike.dld's 4160-02a-path.patch: portability wrapper around file paths.

File size: 10.8 KB
Line 
1/*
2 * This file Copyright (C) 2013-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id$
8 */
9
10#include <assert.h>
11#include <stdlib.h> /* _splitpath_s (), _makepath_s () */
12
13#include "transmission.h"
14#include "file.h"
15#include "utils.h"
16
17/* MSDN (http://msdn.microsoft.com/en-us/library/2k2xf226.aspx) only mentions
18   "i64" suffix for C code, but no warning is issued */
19#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
20
21static void
22set_system_error (tr_error ** error,
23                  DWORD       code)
24{
25  char * message;
26
27  if (error == NULL)
28    return;
29
30  message = tr_win32_format_message (code);
31
32  if (message != NULL)
33    {
34      tr_error_set_literal (error, code, message);
35      tr_free (message);
36    }
37  else
38    {
39      tr_error_set (error, code, "Unknown error: 0x%08x", code);
40    }
41}
42
43static void
44set_system_error_if_file_found (tr_error ** error,
45                                DWORD       code)
46{
47  if (code != ERROR_FILE_NOT_FOUND &&
48      code != ERROR_PATH_NOT_FOUND &&
49      code != ERROR_NO_MORE_FILES)
50    set_system_error (error, code);
51}
52
53static time_t
54filetime_to_unix_time (const FILETIME * t)
55{
56  uint64_t tmp = 0;
57
58  assert (t != NULL);
59
60  tmp |= t->dwHighDateTime;
61  tmp <<= 32;
62  tmp |= t->dwLowDateTime;
63  tmp /= 10; /* to microseconds */
64  tmp -= DELTA_EPOCH_IN_MICROSECS;
65
66  return tmp / 1000000UL;
67}
68
69static void
70stat_to_sys_path_info (DWORD              attributes,
71                       DWORD              size_low,
72                       DWORD              size_high,
73                       const FILETIME   * mtime,
74                       tr_sys_path_info * info)
75{
76  assert (mtime != NULL);
77  assert (info != NULL);
78
79  if (attributes & FILE_ATTRIBUTE_DIRECTORY)
80    info->type = TR_SYS_PATH_IS_DIRECTORY;
81  else if (!(attributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_VIRTUAL)))
82    info->type = TR_SYS_PATH_IS_FILE;
83  else
84    info->type = TR_SYS_PATH_IS_OTHER;
85
86  info->size = size_high;
87  info->size <<= 32;
88  info->size |= size_low;
89
90  info->last_modified_at = filetime_to_unix_time (mtime);
91}
92
93static bool
94get_file_info (HANDLE              handle,
95               tr_sys_path_info  * info,
96               tr_error         ** error);
97
98bool
99tr_sys_path_exists (const char  * path,
100                    tr_error   ** error)
101{
102  bool ret = false;
103  wchar_t * wide_path;
104  HANDLE handle = INVALID_HANDLE_VALUE;
105
106  assert (path != NULL);
107
108  wide_path = tr_win32_utf8_to_native (path, -1);
109
110  if (wide_path != NULL)
111    {
112      DWORD attributes = GetFileAttributesW (wide_path);
113      if (attributes != INVALID_FILE_ATTRIBUTES)
114        {
115          if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
116            {
117              handle = CreateFileW (wide_path, 0, 0, NULL, OPEN_EXISTING,
118                                    FILE_FLAG_BACKUP_SEMANTICS, NULL);
119
120              ret = handle != INVALID_HANDLE_VALUE;
121            }
122          else
123            {
124              ret = true;
125            }
126        }
127    }
128
129  if (!ret)
130    set_system_error_if_file_found (error, GetLastError ());
131
132  if (handle != INVALID_HANDLE_VALUE)
133    CloseHandle (handle);
134
135  tr_free (wide_path);
136
137  return ret;
138}
139
140bool
141tr_sys_path_get_info (const char        * path,
142                      int                 flags,
143                      tr_sys_path_info  * info,
144                      tr_error         ** error)
145{
146  bool ret = false;
147  wchar_t * wide_path;
148
149  assert (path != NULL);
150  assert (info != NULL);
151
152  wide_path = tr_win32_utf8_to_native (path, -1);
153
154  if ((flags & TR_SYS_PATH_NO_FOLLOW) == 0)
155    {
156      HANDLE handle = INVALID_HANDLE_VALUE;
157
158      if (wide_path != NULL)
159        handle = CreateFileW (wide_path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
160
161      if (handle != INVALID_HANDLE_VALUE)
162        {
163          tr_error * my_error = NULL;
164          ret = get_file_info (handle, info, &my_error);
165          if (!ret)
166            tr_error_propagate (error, &my_error);
167          CloseHandle (handle);
168        }
169      else
170        {
171          set_system_error (error, GetLastError ());
172        }
173    }
174  else
175    {
176      WIN32_FILE_ATTRIBUTE_DATA attributes;
177
178      if (wide_path != NULL)
179        ret = GetFileAttributesExW (wide_path, GetFileExInfoStandard, &attributes);
180
181      if (ret)
182        stat_to_sys_path_info (attributes.dwFileAttributes, attributes.nFileSizeLow,
183                               attributes.nFileSizeHigh, &attributes.ftLastWriteTime,
184                               info);
185      else
186        set_system_error (error, GetLastError ());
187    }
188
189  tr_free (wide_path);
190
191  return ret;
192}
193
194bool
195tr_sys_path_is_same (const char  * path1,
196                     const char  * path2,
197                     tr_error   ** error)
198{
199  bool ret = false;
200  wchar_t * wide_path1 = NULL;
201  wchar_t * wide_path2 = NULL;
202  HANDLE handle1 = INVALID_HANDLE_VALUE;
203  HANDLE handle2 = INVALID_HANDLE_VALUE;
204  BY_HANDLE_FILE_INFORMATION fi1, fi2;
205
206  assert (path1 != NULL);
207  assert (path2 != NULL);
208
209  wide_path1 = tr_win32_utf8_to_native (path1, -1);
210  if (wide_path1 == NULL)
211    goto fail;
212
213  wide_path2 = tr_win32_utf8_to_native (path2, -1);
214  if (wide_path2 == NULL)
215    goto fail;
216
217  handle1 = CreateFileW (wide_path1, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
218  if (handle1 == INVALID_HANDLE_VALUE)
219    goto fail;
220
221  handle2 = CreateFileW (wide_path2, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
222  if (handle2 == INVALID_HANDLE_VALUE)
223    goto fail;
224
225  /* TODO: Use GetFileInformationByHandleEx on >= Server 2012 */
226
227  if (!GetFileInformationByHandle (handle1, &fi1) || !GetFileInformationByHandle (handle2, &fi2))
228    goto fail;
229
230  ret = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber &&
231        fi1.nFileIndexHigh == fi2.nFileIndexHigh &&
232        fi1.nFileIndexLow  == fi2.nFileIndexLow;
233
234  goto cleanup;
235
236fail:
237  set_system_error_if_file_found (error, GetLastError ());
238
239cleanup:
240  CloseHandle (handle2);
241  CloseHandle (handle1);
242
243  tr_free (wide_path2);
244  tr_free (wide_path1);
245
246  return ret;
247}
248
249char *
250tr_sys_path_resolve (const char  * path,
251                     tr_error   ** error)
252{
253  char * ret = NULL;
254  wchar_t * wide_path;
255  wchar_t * wide_ret = NULL;
256  HANDLE handle;
257  DWORD wide_ret_size;
258
259  assert (path != NULL);
260
261  wide_path = tr_win32_utf8_to_native (path, -1);
262  if (wide_path == NULL)
263    goto fail;
264
265  handle = CreateFileW (wide_path, FILE_READ_EA,
266                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
267                        NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
268  if (handle == INVALID_HANDLE_VALUE)
269    goto fail;
270
271  wide_ret_size = GetFinalPathNameByHandleW (handle, NULL, 0, 0);
272  if (wide_ret_size == 0)
273    goto fail;
274
275  wide_ret = tr_new (wchar_t, wide_ret_size);
276  if (GetFinalPathNameByHandleW (handle, wide_ret, wide_ret_size, 0) != wide_ret_size - 1)
277    goto fail;
278
279  /* Resolved path always begins with "\\?\", so skip those first four chars. */
280  ret = tr_win32_native_to_utf8 (wide_ret + 4, -1);
281  if (ret != NULL)
282    goto cleanup;
283
284fail:
285  set_system_error (error, GetLastError ());
286
287  tr_free (ret);
288  ret = NULL;
289
290cleanup:
291  tr_free (wide_ret);
292  tr_free (wide_path);
293
294  if (handle != INVALID_HANDLE_VALUE)
295    CloseHandle (handle);
296
297  return ret;
298}
299
300char *
301tr_sys_path_basename (const char  * path,
302                      tr_error   ** error)
303{
304  char fname[_MAX_FNAME], ext[_MAX_EXT];
305
306  assert (path != NULL);
307
308  /* TODO: Error handling */
309
310  if (_splitpath_s (path, NULL, 0, NULL, 0, fname, sizeof (fname), ext, sizeof (ext)) == 0 &&
311      (*fname != '\0' || *ext != '\0'))
312    {
313      const size_t tmp_len = strlen (fname) + strlen (ext) + 2;
314      char * const tmp = tr_new (char, tmp_len);
315      if (_makepath_s (tmp, tmp_len, NULL, NULL, fname, ext) == 0)
316        return tmp;
317      tr_free (tmp);
318    }
319
320  return tr_strdup (".");
321}
322
323char *
324tr_sys_path_dirname (const char  * path,
325                     tr_error   ** error)
326{
327  char drive[_MAX_DRIVE], dir[_MAX_DIR];
328
329  assert (path != NULL);
330
331  /* TODO: Error handling */
332
333  if (_splitpath_s (path, drive, sizeof (drive), dir, sizeof (dir), NULL, 0, NULL, 0) == 0 &&
334      (*drive != '\0' || *dir != '\0'))
335    {
336      const size_t tmp_len = strlen (drive) + strlen (dir) + 2;
337      char * const tmp = tr_new (char, tmp_len);
338      if (_makepath_s (tmp, tmp_len, drive, dir, NULL, NULL) == 0)
339        {
340          size_t len = strlen(tmp);
341          while (len > 0 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\'))
342            tmp[--len] = '\0';
343
344          return tmp;
345        }
346
347      tr_free (tmp);
348    }
349
350  return tr_strdup (".");
351}
352
353bool
354tr_sys_path_rename (const char  * src_path,
355                    const char  * dst_path,
356                    tr_error   ** error)
357{
358  bool ret = false;
359  wchar_t * wide_src_path;
360  wchar_t * wide_dst_path;
361
362  assert (src_path != NULL);
363  assert (dst_path != NULL);
364
365  wide_src_path = tr_win32_utf8_to_native (src_path, -1);
366  wide_dst_path = tr_win32_utf8_to_native (dst_path, -1);
367
368  if (wide_src_path != NULL && wide_dst_path != NULL)
369    {
370      DWORD flags = MOVEFILE_REPLACE_EXISTING;
371      DWORD attributes;
372
373      attributes = GetFileAttributesW (wide_src_path);
374      if (attributes != INVALID_FILE_ATTRIBUTES &&
375          (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
376        {
377          flags = 0;
378        }
379      else
380        {
381          attributes = GetFileAttributesW (wide_dst_path);
382          if (attributes != INVALID_FILE_ATTRIBUTES &&
383              (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
384            flags = 0;
385        }
386
387      ret = MoveFileExW (wide_src_path, wide_dst_path, flags);
388    }
389
390  if (!ret)
391    set_system_error (error, GetLastError ());
392
393  tr_free (wide_dst_path);
394  tr_free (wide_src_path);
395
396  return ret;
397}
398
399bool
400tr_sys_path_remove (const char  * path,
401                    tr_error   ** error)
402{
403  bool ret = false;
404  wchar_t * wide_path;
405
406  assert (path != NULL);
407
408  wide_path = tr_win32_utf8_to_native (path, -1);
409
410  if (wide_path != NULL)
411    {
412      const DWORD attributes = GetFileAttributesW (wide_path);
413
414      if (attributes != INVALID_FILE_ATTRIBUTES)
415        {
416          if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
417            ret = RemoveDirectoryW (wide_path);
418          else
419            ret = DeleteFileW (wide_path);
420        }
421    }
422
423  if (!ret)
424    set_system_error (error, GetLastError ());
425
426  tr_free (wide_path);
427
428  return ret;
429}
430
431static bool
432get_file_info (HANDLE              handle,
433               tr_sys_path_info  * info,
434               tr_error         ** error)
435{
436  bool ret;
437  BY_HANDLE_FILE_INFORMATION attributes;
438
439  assert (handle != INVALID_HANDLE_VALUE);
440  assert (info != NULL);
441
442  ret = GetFileInformationByHandle (handle, &attributes);
443
444  if (ret)
445    stat_to_sys_path_info (attributes.dwFileAttributes, attributes.nFileSizeLow,
446                           attributes.nFileSizeHigh, &attributes.ftLastWriteTime,
447                           info);
448  else
449    set_system_error (error, GetLastError ());
450
451  return ret;
452}
Note: See TracBrowser for help on using the repository browser.