nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* GIO - GLib Input, Output and Streaming Library |
2 | * |
||
3 | * Copyright (C) 2006-2007 Red Hat, Inc. |
||
4 | * |
||
5 | * This library is free software; you can redistribute it and/or |
||
6 | * modify it under the terms of the GNU Lesser General Public |
||
7 | * License as published by the Free Software Foundation; either |
||
8 | * version 2 of the License, or (at your option) any later version. |
||
9 | * |
||
10 | * This library is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
13 | * Lesser General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU Lesser General |
||
16 | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
||
17 | * |
||
18 | * Author: Alexander Larsson <alexl@redhat.com> |
||
19 | */ |
||
20 | |||
21 | #include "config.h" |
||
22 | |||
23 | #include <sys/types.h> |
||
24 | #include <sys/stat.h> |
||
25 | #include <string.h> |
||
26 | #include <errno.h> |
||
27 | #include <fcntl.h> |
||
28 | #if G_OS_UNIX |
||
29 | #include <dirent.h> |
||
30 | #include <unistd.h> |
||
31 | #endif |
||
32 | |||
33 | #if HAVE_SYS_STATFS_H |
||
34 | #include <sys/statfs.h> |
||
35 | #endif |
||
36 | #if HAVE_SYS_STATVFS_H |
||
37 | #include <sys/statvfs.h> |
||
38 | #endif |
||
39 | #if HAVE_SYS_VFS_H |
||
40 | #include <sys/vfs.h> |
||
41 | #elif HAVE_SYS_MOUNT_H |
||
42 | #if HAVE_SYS_PARAM_H |
||
43 | #include <sys/param.h> |
||
44 | #endif |
||
45 | #include <sys/mount.h> |
||
46 | #endif |
||
47 | |||
48 | #ifndef O_BINARY |
||
49 | #define O_BINARY 0 |
||
50 | #endif |
||
51 | |||
52 | #include "gfileattribute.h" |
||
53 | #include "glocalfile.h" |
||
54 | #include "glocalfileinfo.h" |
||
55 | #include "glocalfileenumerator.h" |
||
56 | #include "glocalfileinputstream.h" |
||
57 | #include "glocalfileoutputstream.h" |
||
58 | #include "glocalfileiostream.h" |
||
59 | #include "glocalfilemonitor.h" |
||
60 | #include "gmountprivate.h" |
||
61 | #include "gunixmounts.h" |
||
62 | #include "gioerror.h" |
||
63 | #include <glib/gstdio.h> |
||
64 | #include "glibintl.h" |
||
65 | #ifdef G_OS_UNIX |
||
66 | #include "glib-unix.h" |
||
67 | #endif |
||
68 | #include "glib-private.h" |
||
69 | |||
70 | #include "glib-private.h" |
||
71 | |||
72 | #ifdef G_OS_WIN32 |
||
73 | #include <windows.h> |
||
74 | #include <io.h> |
||
75 | #include <direct.h> |
||
76 | |||
77 | #ifndef FILE_READ_ONLY_VOLUME |
||
78 | #define FILE_READ_ONLY_VOLUME 0x00080000 |
||
79 | #endif |
||
80 | |||
81 | #ifndef S_ISDIR |
||
82 | #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) |
||
83 | #endif |
||
84 | #ifndef S_ISLNK |
||
85 | #define S_ISLNK(m) (0) |
||
86 | #endif |
||
87 | #endif |
||
88 | |||
89 | |||
90 | static void g_local_file_file_iface_init (GFileIface *iface); |
||
91 | |||
92 | static GFileAttributeInfoList *local_writable_attributes = NULL; |
||
93 | static /* GFileAttributeInfoList * */ gsize local_writable_namespaces = 0; |
||
94 | |||
95 | struct _GLocalFile |
||
96 | { |
||
97 | GObject parent_instance; |
||
98 | |||
99 | char *filename; |
||
100 | }; |
||
101 | |||
102 | #define g_local_file_get_type _g_local_file_get_type |
||
103 | G_DEFINE_TYPE_WITH_CODE (GLocalFile, g_local_file, G_TYPE_OBJECT, |
||
104 | G_IMPLEMENT_INTERFACE (G_TYPE_FILE, |
||
105 | g_local_file_file_iface_init)) |
||
106 | |||
107 | static char *find_mountpoint_for (const char *file, dev_t dev); |
||
108 | |||
109 | static void |
||
110 | g_local_file_finalize (GObject *object) |
||
111 | { |
||
112 | GLocalFile *local; |
||
113 | |||
114 | local = G_LOCAL_FILE (object); |
||
115 | |||
116 | g_free (local->filename); |
||
117 | |||
118 | G_OBJECT_CLASS (g_local_file_parent_class)->finalize (object); |
||
119 | } |
||
120 | |||
121 | static void |
||
122 | g_local_file_class_init (GLocalFileClass *klass) |
||
123 | { |
||
124 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
||
125 | GFileAttributeInfoList *list; |
||
126 | |||
127 | gobject_class->finalize = g_local_file_finalize; |
||
128 | |||
129 | /* Set up attribute lists */ |
||
130 | |||
131 | /* Writable attributes: */ |
||
132 | |||
133 | list = g_file_attribute_info_list_new (); |
||
134 | |||
135 | g_file_attribute_info_list_add (list, |
||
136 | G_FILE_ATTRIBUTE_UNIX_MODE, |
||
137 | G_FILE_ATTRIBUTE_TYPE_UINT32, |
||
138 | G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | |
||
139 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
140 | |||
141 | #ifdef G_OS_UNIX |
||
142 | g_file_attribute_info_list_add (list, |
||
143 | G_FILE_ATTRIBUTE_UNIX_UID, |
||
144 | G_FILE_ATTRIBUTE_TYPE_UINT32, |
||
145 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
146 | g_file_attribute_info_list_add (list, |
||
147 | G_FILE_ATTRIBUTE_UNIX_GID, |
||
148 | G_FILE_ATTRIBUTE_TYPE_UINT32, |
||
149 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
150 | #endif |
||
151 | |||
152 | #ifdef HAVE_SYMLINK |
||
153 | g_file_attribute_info_list_add (list, |
||
154 | G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, |
||
155 | G_FILE_ATTRIBUTE_TYPE_BYTE_STRING, |
||
156 | 0); |
||
157 | #endif |
||
158 | |||
159 | #ifdef HAVE_UTIMES |
||
160 | g_file_attribute_info_list_add (list, |
||
161 | G_FILE_ATTRIBUTE_TIME_MODIFIED, |
||
162 | G_FILE_ATTRIBUTE_TYPE_UINT64, |
||
163 | G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | |
||
164 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
165 | g_file_attribute_info_list_add (list, |
||
166 | G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, |
||
167 | G_FILE_ATTRIBUTE_TYPE_UINT32, |
||
168 | G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | |
||
169 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
170 | /* When copying, the target file is accessed. Replicating |
||
171 | * the source access time does not make sense in this case. |
||
172 | */ |
||
173 | g_file_attribute_info_list_add (list, |
||
174 | G_FILE_ATTRIBUTE_TIME_ACCESS, |
||
175 | G_FILE_ATTRIBUTE_TYPE_UINT64, |
||
176 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
177 | g_file_attribute_info_list_add (list, |
||
178 | G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, |
||
179 | G_FILE_ATTRIBUTE_TYPE_UINT32, |
||
180 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
181 | #endif |
||
182 | |||
183 | local_writable_attributes = list; |
||
184 | } |
||
185 | |||
186 | static void |
||
187 | g_local_file_init (GLocalFile *local) |
||
188 | { |
||
189 | } |
||
190 | |||
191 | const char * |
||
192 | _g_local_file_get_filename (GLocalFile *file) |
||
193 | { |
||
194 | return file->filename; |
||
195 | } |
||
196 | |||
197 | static char * |
||
198 | canonicalize_filename (const char *filename) |
||
199 | { |
||
200 | char *canon, *start, *p, *q; |
||
201 | char *cwd; |
||
202 | int i; |
||
203 | |||
204 | if (!g_path_is_absolute (filename)) |
||
205 | { |
||
206 | cwd = g_get_current_dir (); |
||
207 | canon = g_build_filename (cwd, filename, NULL); |
||
208 | g_free (cwd); |
||
209 | } |
||
210 | else |
||
211 | canon = g_strdup (filename); |
||
212 | |||
213 | start = (char *)g_path_skip_root (canon); |
||
214 | |||
215 | if (start == NULL) |
||
216 | { |
||
217 | /* This shouldn't really happen, as g_get_current_dir() should |
||
218 | return an absolute pathname, but bug 573843 shows this is |
||
219 | not always happening */ |
||
220 | g_free (canon); |
||
221 | return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL); |
||
222 | } |
||
223 | |||
224 | /* POSIX allows double slashes at the start to |
||
225 | * mean something special (as does windows too). |
||
226 | * So, "//" != "/", but more than two slashes |
||
227 | * is treated as "/". |
||
228 | */ |
||
229 | i = 0; |
||
230 | for (p = start - 1; |
||
231 | (p >= canon) && |
||
232 | G_IS_DIR_SEPARATOR (*p); |
||
233 | p--) |
||
234 | i++; |
||
235 | if (i > 2) |
||
236 | { |
||
237 | i -= 1; |
||
238 | start -= i; |
||
239 | memmove (start, start+i, strlen (start+i)+1); |
||
240 | } |
||
241 | |||
242 | /* Make sure we're using the canonical dir separator */ |
||
243 | p++; |
||
244 | while (p < start && G_IS_DIR_SEPARATOR (*p)) |
||
245 | *p++ = G_DIR_SEPARATOR; |
||
246 | |||
247 | p = start; |
||
248 | while (*p != 0) |
||
249 | { |
||
250 | if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1]))) |
||
251 | { |
||
252 | memmove (p, p+1, strlen (p+1)+1); |
||
253 | } |
||
254 | else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2]))) |
||
255 | { |
||
256 | q = p + 2; |
||
257 | /* Skip previous separator */ |
||
258 | p = p - 2; |
||
259 | if (p < start) |
||
260 | p = start; |
||
261 | while (p > start && !G_IS_DIR_SEPARATOR (*p)) |
||
262 | p--; |
||
263 | if (G_IS_DIR_SEPARATOR (*p)) |
||
264 | *p++ = G_DIR_SEPARATOR; |
||
265 | memmove (p, q, strlen (q)+1); |
||
266 | } |
||
267 | else |
||
268 | { |
||
269 | /* Skip until next separator */ |
||
270 | while (*p != 0 && !G_IS_DIR_SEPARATOR (*p)) |
||
271 | p++; |
||
272 | |||
273 | if (*p != 0) |
||
274 | { |
||
275 | /* Canonicalize one separator */ |
||
276 | *p++ = G_DIR_SEPARATOR; |
||
277 | } |
||
278 | } |
||
279 | |||
280 | /* Remove additional separators */ |
||
281 | q = p; |
||
282 | while (*q && G_IS_DIR_SEPARATOR (*q)) |
||
283 | q++; |
||
284 | |||
285 | if (p != q) |
||
286 | memmove (p, q, strlen (q)+1); |
||
287 | } |
||
288 | |||
289 | /* Remove trailing slashes */ |
||
290 | if (p > start && G_IS_DIR_SEPARATOR (*(p-1))) |
||
291 | *(p-1) = 0; |
||
292 | |||
293 | return canon; |
||
294 | } |
||
295 | |||
296 | GFile * |
||
297 | _g_local_file_new (const char *filename) |
||
298 | { |
||
299 | GLocalFile *local; |
||
300 | |||
301 | local = g_object_new (G_TYPE_LOCAL_FILE, NULL); |
||
302 | local->filename = canonicalize_filename (filename); |
||
303 | |||
304 | return G_FILE (local); |
||
305 | } |
||
306 | |||
307 | /*< internal > |
||
308 | * g_local_file_new_from_dirname_and_basename: |
||
309 | * @dirname: an absolute, canonical directory name |
||
310 | * @basename: the name of a child inside @dirname |
||
311 | * |
||
312 | * Creates a #GFile from @dirname and @basename. |
||
313 | * |
||
314 | * This is more efficient than pasting the fields together for yourself |
||
315 | * and creating a #GFile from the result, and also more efficient than |
||
316 | * creating a #GFile for the dirname and using g_file_get_child(). |
||
317 | * |
||
318 | * @dirname must be canonical, as per GLocalFile's opinion of what |
||
319 | * canonical means. This means that you should only pass strings that |
||
320 | * were returned by _g_local_file_get_filename(). |
||
321 | * |
||
322 | * Returns: a #GFile |
||
323 | */ |
||
324 | GFile * |
||
325 | g_local_file_new_from_dirname_and_basename (const gchar *dirname, |
||
326 | const gchar *basename) |
||
327 | { |
||
328 | GLocalFile *local; |
||
329 | |||
330 | g_return_val_if_fail (dirname != NULL, NULL); |
||
331 | g_return_val_if_fail (basename && basename[0] && !strchr (basename, '/'), NULL); |
||
332 | |||
333 | local = g_object_new (G_TYPE_LOCAL_FILE, NULL); |
||
334 | local->filename = g_build_filename (dirname, basename, NULL); |
||
335 | |||
336 | return G_FILE (local); |
||
337 | } |
||
338 | |||
339 | static gboolean |
||
340 | g_local_file_is_native (GFile *file) |
||
341 | { |
||
342 | return TRUE; |
||
343 | } |
||
344 | |||
345 | static gboolean |
||
346 | g_local_file_has_uri_scheme (GFile *file, |
||
347 | const char *uri_scheme) |
||
348 | { |
||
349 | return g_ascii_strcasecmp (uri_scheme, "file") == 0; |
||
350 | } |
||
351 | |||
352 | static char * |
||
353 | g_local_file_get_uri_scheme (GFile *file) |
||
354 | { |
||
355 | return g_strdup ("file"); |
||
356 | } |
||
357 | |||
358 | static char * |
||
359 | g_local_file_get_basename (GFile *file) |
||
360 | { |
||
361 | return g_path_get_basename (G_LOCAL_FILE (file)->filename); |
||
362 | } |
||
363 | |||
364 | static char * |
||
365 | g_local_file_get_path (GFile *file) |
||
366 | { |
||
367 | return g_strdup (G_LOCAL_FILE (file)->filename); |
||
368 | } |
||
369 | |||
370 | static char * |
||
371 | g_local_file_get_uri (GFile *file) |
||
372 | { |
||
373 | return g_filename_to_uri (G_LOCAL_FILE (file)->filename, NULL, NULL); |
||
374 | } |
||
375 | |||
376 | static gboolean |
||
377 | get_filename_charset (const gchar **filename_charset) |
||
378 | { |
||
379 | const gchar **charsets; |
||
380 | gboolean is_utf8; |
||
381 | |||
382 | is_utf8 = g_get_filename_charsets (&charsets); |
||
383 | |||
384 | if (filename_charset) |
||
385 | *filename_charset = charsets[0]; |
||
386 | |||
387 | return is_utf8; |
||
388 | } |
||
389 | |||
390 | static gboolean |
||
391 | name_is_valid_for_display (const char *string, |
||
392 | gboolean is_valid_utf8) |
||
393 | { |
||
394 | char c; |
||
395 | |||
396 | if (!is_valid_utf8 && |
||
397 | !g_utf8_validate (string, -1, NULL)) |
||
398 | return FALSE; |
||
399 | |||
400 | while ((c = *string++) != 0) |
||
401 | { |
||
402 | if (g_ascii_iscntrl (c)) |
||
403 | return FALSE; |
||
404 | } |
||
405 | |||
406 | return TRUE; |
||
407 | } |
||
408 | |||
409 | static char * |
||
410 | g_local_file_get_parse_name (GFile *file) |
||
411 | { |
||
412 | const char *filename; |
||
413 | char *parse_name; |
||
414 | const gchar *charset; |
||
415 | char *utf8_filename; |
||
416 | char *roundtripped_filename; |
||
417 | gboolean free_utf8_filename; |
||
418 | gboolean is_valid_utf8; |
||
419 | char *escaped_path; |
||
420 | |||
421 | filename = G_LOCAL_FILE (file)->filename; |
||
422 | if (get_filename_charset (&charset)) |
||
423 | { |
||
424 | utf8_filename = (char *)filename; |
||
425 | free_utf8_filename = FALSE; |
||
426 | is_valid_utf8 = FALSE; /* Can't guarantee this */ |
||
427 | } |
||
428 | else |
||
429 | { |
||
430 | utf8_filename = g_convert (filename, -1, |
||
431 | "UTF-8", charset, NULL, NULL, NULL); |
||
432 | free_utf8_filename = TRUE; |
||
433 | is_valid_utf8 = TRUE; |
||
434 | |||
435 | if (utf8_filename != NULL) |
||
436 | { |
||
437 | /* Make sure we can roundtrip: */ |
||
438 | roundtripped_filename = g_convert (utf8_filename, -1, |
||
439 | charset, "UTF-8", NULL, NULL, NULL); |
||
440 | |||
441 | if (roundtripped_filename == NULL || |
||
442 | strcmp (filename, roundtripped_filename) != 0) |
||
443 | { |
||
444 | g_free (utf8_filename); |
||
445 | utf8_filename = NULL; |
||
446 | } |
||
447 | |||
448 | g_free (roundtripped_filename); |
||
449 | } |
||
450 | } |
||
451 | |||
452 | if (utf8_filename != NULL && |
||
453 | name_is_valid_for_display (utf8_filename, is_valid_utf8)) |
||
454 | { |
||
455 | if (free_utf8_filename) |
||
456 | parse_name = utf8_filename; |
||
457 | else |
||
458 | parse_name = g_strdup (utf8_filename); |
||
459 | } |
||
460 | else |
||
461 | { |
||
462 | #ifdef G_OS_WIN32 |
||
463 | char *dup_filename, *p, *backslash; |
||
464 | |||
465 | /* Turn backslashes into forward slashes like |
||
466 | * g_filename_to_uri() would do (but we can't use that because |
||
467 | * it doesn't output IRIs). |
||
468 | */ |
||
469 | dup_filename = g_strdup (filename); |
||
470 | filename = p = dup_filename; |
||
471 | |||
472 | while ((backslash = strchr (p, '\\')) != NULL) |
||
473 | { |
||
474 | *backslash = '/'; |
||
475 | p = backslash + 1; |
||
476 | } |
||
477 | #endif |
||
478 | |||
479 | escaped_path = g_uri_escape_string (filename, |
||
480 | G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT "/", |
||
481 | TRUE); |
||
482 | parse_name = g_strconcat ("file://", |
||
483 | (*escaped_path != '/') ? "/" : "", |
||
484 | escaped_path, |
||
485 | NULL); |
||
486 | |||
487 | g_free (escaped_path); |
||
488 | #ifdef G_OS_WIN32 |
||
489 | g_free (dup_filename); |
||
490 | #endif |
||
491 | if (free_utf8_filename) |
||
492 | g_free (utf8_filename); |
||
493 | } |
||
494 | |||
495 | return parse_name; |
||
496 | } |
||
497 | |||
498 | static GFile * |
||
499 | g_local_file_get_parent (GFile *file) |
||
500 | { |
||
501 | GLocalFile *local = G_LOCAL_FILE (file); |
||
502 | const char *non_root; |
||
503 | char *dirname; |
||
504 | GFile *parent; |
||
505 | |||
506 | /* Check for root; local->filename is guaranteed to be absolute, so |
||
507 | * g_path_skip_root() should never return NULL. */ |
||
508 | non_root = g_path_skip_root (local->filename); |
||
509 | g_assert (non_root != NULL); |
||
510 | |||
511 | if (*non_root == 0) |
||
512 | return NULL; |
||
513 | |||
514 | dirname = g_path_get_dirname (local->filename); |
||
515 | parent = _g_local_file_new (dirname); |
||
516 | g_free (dirname); |
||
517 | return parent; |
||
518 | } |
||
519 | |||
520 | static GFile * |
||
521 | g_local_file_dup (GFile *file) |
||
522 | { |
||
523 | GLocalFile *local = G_LOCAL_FILE (file); |
||
524 | |||
525 | return _g_local_file_new (local->filename); |
||
526 | } |
||
527 | |||
528 | static guint |
||
529 | g_local_file_hash (GFile *file) |
||
530 | { |
||
531 | GLocalFile *local = G_LOCAL_FILE (file); |
||
532 | |||
533 | return g_str_hash (local->filename); |
||
534 | } |
||
535 | |||
536 | static gboolean |
||
537 | g_local_file_equal (GFile *file1, |
||
538 | GFile *file2) |
||
539 | { |
||
540 | GLocalFile *local1 = G_LOCAL_FILE (file1); |
||
541 | GLocalFile *local2 = G_LOCAL_FILE (file2); |
||
542 | |||
543 | return g_str_equal (local1->filename, local2->filename); |
||
544 | } |
||
545 | |||
546 | static const char * |
||
547 | match_prefix (const char *path, |
||
548 | const char *prefix) |
||
549 | { |
||
550 | int prefix_len; |
||
551 | |||
552 | prefix_len = strlen (prefix); |
||
553 | if (strncmp (path, prefix, prefix_len) != 0) |
||
554 | return NULL; |
||
555 | |||
556 | /* Handle the case where prefix is the root, so that |
||
557 | * the IS_DIR_SEPRARATOR check below works */ |
||
558 | if (prefix_len > 0 && |
||
559 | G_IS_DIR_SEPARATOR (prefix[prefix_len-1])) |
||
560 | prefix_len--; |
||
561 | |||
562 | return path + prefix_len; |
||
563 | } |
||
564 | |||
565 | static gboolean |
||
566 | g_local_file_prefix_matches (GFile *parent, |
||
567 | GFile *descendant) |
||
568 | { |
||
569 | GLocalFile *parent_local = G_LOCAL_FILE (parent); |
||
570 | GLocalFile *descendant_local = G_LOCAL_FILE (descendant); |
||
571 | const char *remainder; |
||
572 | |||
573 | remainder = match_prefix (descendant_local->filename, parent_local->filename); |
||
574 | if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder)) |
||
575 | return TRUE; |
||
576 | return FALSE; |
||
577 | } |
||
578 | |||
579 | static char * |
||
580 | g_local_file_get_relative_path (GFile *parent, |
||
581 | GFile *descendant) |
||
582 | { |
||
583 | GLocalFile *parent_local = G_LOCAL_FILE (parent); |
||
584 | GLocalFile *descendant_local = G_LOCAL_FILE (descendant); |
||
585 | const char *remainder; |
||
586 | |||
587 | remainder = match_prefix (descendant_local->filename, parent_local->filename); |
||
588 | |||
589 | if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder)) |
||
590 | return g_strdup (remainder + 1); |
||
591 | return NULL; |
||
592 | } |
||
593 | |||
594 | static GFile * |
||
595 | g_local_file_resolve_relative_path (GFile *file, |
||
596 | const char *relative_path) |
||
597 | { |
||
598 | GLocalFile *local = G_LOCAL_FILE (file); |
||
599 | char *filename; |
||
600 | GFile *child; |
||
601 | |||
602 | if (g_path_is_absolute (relative_path)) |
||
603 | return _g_local_file_new (relative_path); |
||
604 | |||
605 | filename = g_build_filename (local->filename, relative_path, NULL); |
||
606 | child = _g_local_file_new (filename); |
||
607 | g_free (filename); |
||
608 | |||
609 | return child; |
||
610 | } |
||
611 | |||
612 | static GFileEnumerator * |
||
613 | g_local_file_enumerate_children (GFile *file, |
||
614 | const char *attributes, |
||
615 | GFileQueryInfoFlags flags, |
||
616 | GCancellable *cancellable, |
||
617 | GError **error) |
||
618 | { |
||
619 | GLocalFile *local = G_LOCAL_FILE (file); |
||
620 | return _g_local_file_enumerator_new (local, |
||
621 | attributes, flags, |
||
622 | cancellable, error); |
||
623 | } |
||
624 | |||
625 | static GFile * |
||
626 | g_local_file_get_child_for_display_name (GFile *file, |
||
627 | const char *display_name, |
||
628 | GError **error) |
||
629 | { |
||
630 | GFile *new_file; |
||
631 | char *basename; |
||
632 | |||
633 | basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL); |
||
634 | if (basename == NULL) |
||
635 | { |
||
636 | g_set_error (error, G_IO_ERROR, |
||
637 | G_IO_ERROR_INVALID_FILENAME, |
||
638 | _("Invalid filename %s"), display_name); |
||
639 | return NULL; |
||
640 | } |
||
641 | |||
642 | new_file = g_file_get_child (file, basename); |
||
643 | g_free (basename); |
||
644 | |||
645 | return new_file; |
||
646 | } |
||
647 | |||
648 | #if defined(USE_STATFS) && !defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) |
||
649 | static const char * |
||
650 | get_fs_type (long f_type) |
||
651 | { |
||
652 | /* filesystem ids taken from linux manpage */ |
||
653 | switch (f_type) |
||
654 | { |
||
655 | case 0xadf5: |
||
656 | return "adfs"; |
||
657 | case 0x5346414f: |
||
658 | return "afs"; |
||
659 | case 0x0187: |
||
660 | return "autofs"; |
||
661 | case 0xADFF: |
||
662 | return "affs"; |
||
663 | case 0x42465331: |
||
664 | return "befs"; |
||
665 | case 0x1BADFACE: |
||
666 | return "bfs"; |
||
667 | case 0x9123683E: |
||
668 | return "btrfs"; |
||
669 | case 0xFF534D42: |
||
670 | return "cifs"; |
||
671 | case 0x73757245: |
||
672 | return "coda"; |
||
673 | case 0x012FF7B7: |
||
674 | return "coh"; |
||
675 | case 0x28cd3d45: |
||
676 | return "cramfs"; |
||
677 | case 0x1373: |
||
678 | return "devfs"; |
||
679 | case 0x00414A53: |
||
680 | return "efs"; |
||
681 | case 0x137D: |
||
682 | return "ext"; |
||
683 | case 0xEF51: |
||
684 | return "ext2"; |
||
685 | case 0xEF53: |
||
686 | return "ext3/ext4"; |
||
687 | case 0x4244: |
||
688 | return "hfs"; |
||
689 | case 0xF995E849: |
||
690 | return "hpfs"; |
||
691 | case 0x958458f6: |
||
692 | return "hugetlbfs"; |
||
693 | case 0x9660: |
||
694 | return "isofs"; |
||
695 | case 0x72b6: |
||
696 | return "jffs2"; |
||
697 | case 0x3153464a: |
||
698 | return "jfs"; |
||
699 | case 0x137F: |
||
700 | return "minix"; |
||
701 | case 0x138F: |
||
702 | return "minix2"; |
||
703 | case 0x2468: |
||
704 | return "minix2"; |
||
705 | case 0x2478: |
||
706 | return "minix22"; |
||
707 | case 0x4d44: |
||
708 | return "msdos"; |
||
709 | case 0x564c: |
||
710 | return "ncp"; |
||
711 | case 0x6969: |
||
712 | return "nfs"; |
||
713 | case 0x5346544e: |
||
714 | return "ntfs"; |
||
715 | case 0x9fa1: |
||
716 | return "openprom"; |
||
717 | case 0x9fa0: |
||
718 | return "proc"; |
||
719 | case 0x002f: |
||
720 | return "qnx4"; |
||
721 | case 0x52654973: |
||
722 | return "reiserfs"; |
||
723 | case 0x7275: |
||
724 | return "romfs"; |
||
725 | case 0x517B: |
||
726 | return "smb"; |
||
727 | case 0x73717368: |
||
728 | return "squashfs"; |
||
729 | case 0x012FF7B6: |
||
730 | return "sysv2"; |
||
731 | case 0x012FF7B5: |
||
732 | return "sysv4"; |
||
733 | case 0x01021994: |
||
734 | return "tmpfs"; |
||
735 | case 0x15013346: |
||
736 | return "udf"; |
||
737 | case 0x00011954: |
||
738 | return "ufs"; |
||
739 | case 0x9fa2: |
||
740 | return "usbdevice"; |
||
741 | case 0xa501FCF5: |
||
742 | return "vxfs"; |
||
743 | case 0x012FF7B4: |
||
744 | return "xenix"; |
||
745 | case 0x58465342: |
||
746 | return "xfs"; |
||
747 | case 0x012FD16D: |
||
748 | return "xiafs"; |
||
749 | case 0x52345362: |
||
750 | return "reiser4"; |
||
751 | default: |
||
752 | return NULL; |
||
753 | } |
||
754 | } |
||
755 | #endif |
||
756 | |||
757 | #ifndef G_OS_WIN32 |
||
758 | |||
759 | G_LOCK_DEFINE_STATIC(mount_info_hash); |
||
760 | static GHashTable *mount_info_hash = NULL; |
||
761 | static guint64 mount_info_hash_cache_time = 0; |
||
762 | |||
763 | typedef enum { |
||
764 | MOUNT_INFO_READONLY = 1<<0 |
||
765 | } MountInfo; |
||
766 | |||
767 | static gboolean |
||
768 | device_equal (gconstpointer v1, |
||
769 | gconstpointer v2) |
||
770 | { |
||
771 | return *(dev_t *)v1 == *(dev_t *)v2; |
||
772 | } |
||
773 | |||
774 | static guint |
||
775 | device_hash (gconstpointer v) |
||
776 | { |
||
777 | return (guint) *(dev_t *)v; |
||
778 | } |
||
779 | |||
780 | static void |
||
781 | get_mount_info (GFileInfo *fs_info, |
||
782 | const char *path, |
||
783 | GFileAttributeMatcher *matcher) |
||
784 | { |
||
785 | GStatBuf buf; |
||
786 | gboolean got_info; |
||
787 | gpointer info_as_ptr; |
||
788 | guint mount_info; |
||
789 | char *mountpoint; |
||
790 | dev_t *dev; |
||
791 | GUnixMountEntry *mount; |
||
792 | guint64 cache_time; |
||
793 | |||
794 | if (g_lstat (path, &buf) != 0) |
||
795 | return; |
||
796 | |||
797 | G_LOCK (mount_info_hash); |
||
798 | |||
799 | if (mount_info_hash == NULL) |
||
800 | mount_info_hash = g_hash_table_new_full (device_hash, device_equal, |
||
801 | g_free, NULL); |
||
802 | |||
803 | |||
804 | if (g_unix_mounts_changed_since (mount_info_hash_cache_time)) |
||
805 | g_hash_table_remove_all (mount_info_hash); |
||
806 | |||
807 | got_info = g_hash_table_lookup_extended (mount_info_hash, |
||
808 | &buf.st_dev, |
||
809 | NULL, |
||
810 | &info_as_ptr); |
||
811 | |||
812 | G_UNLOCK (mount_info_hash); |
||
813 | |||
814 | mount_info = GPOINTER_TO_UINT (info_as_ptr); |
||
815 | |||
816 | if (!got_info) |
||
817 | { |
||
818 | mount_info = 0; |
||
819 | |||
820 | mountpoint = find_mountpoint_for (path, buf.st_dev); |
||
821 | if (mountpoint == NULL) |
||
822 | mountpoint = g_strdup ("/"); |
||
823 | |||
824 | mount = g_unix_mount_at (mountpoint, &cache_time); |
||
825 | if (mount) |
||
826 | { |
||
827 | if (g_unix_mount_is_readonly (mount)) |
||
828 | mount_info |= MOUNT_INFO_READONLY; |
||
829 | |||
830 | g_unix_mount_free (mount); |
||
831 | } |
||
832 | |||
833 | g_free (mountpoint); |
||
834 | |||
835 | dev = g_new0 (dev_t, 1); |
||
836 | *dev = buf.st_dev; |
||
837 | |||
838 | G_LOCK (mount_info_hash); |
||
839 | mount_info_hash_cache_time = cache_time; |
||
840 | g_hash_table_insert (mount_info_hash, dev, GUINT_TO_POINTER (mount_info)); |
||
841 | G_UNLOCK (mount_info_hash); |
||
842 | } |
||
843 | |||
844 | if (mount_info & MOUNT_INFO_READONLY) |
||
845 | g_file_info_set_attribute_boolean (fs_info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); |
||
846 | } |
||
847 | |||
848 | #endif |
||
849 | |||
850 | #ifdef G_OS_WIN32 |
||
851 | |||
852 | static gboolean |
||
853 | is_xp_or_later (void) |
||
854 | { |
||
855 | static int result = -1; |
||
856 | |||
857 | if (result == -1) |
||
858 | { |
||
859 | #ifndef _MSC_VER |
||
860 | OSVERSIONINFOEX ver_info = {0}; |
||
861 | DWORDLONG cond_mask = 0; |
||
862 | int op = VER_GREATER_EQUAL; |
||
863 | |||
864 | ver_info.dwOSVersionInfoSize = sizeof ver_info; |
||
865 | ver_info.dwMajorVersion = 5; |
||
866 | ver_info.dwMinorVersion = 1; |
||
867 | |||
868 | VER_SET_CONDITION (cond_mask, VER_MAJORVERSION, op); |
||
869 | VER_SET_CONDITION (cond_mask, VER_MINORVERSION, op); |
||
870 | |||
871 | result = VerifyVersionInfo (&ver_info, |
||
872 | VER_MAJORVERSION | VER_MINORVERSION, |
||
873 | cond_mask) != 0; |
||
874 | #else |
||
875 | result = ((DWORD)(LOBYTE (LOWORD (GetVersion ())))) >= 5; |
||
876 | #endif |
||
877 | } |
||
878 | |||
879 | return result; |
||
880 | } |
||
881 | |||
882 | static wchar_t * |
||
883 | get_volume_for_path (const char *path) |
||
884 | { |
||
885 | long len; |
||
886 | wchar_t *wpath; |
||
887 | wchar_t *result; |
||
888 | |||
889 | wpath = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL); |
||
890 | result = g_new (wchar_t, MAX_PATH); |
||
891 | |||
892 | if (!GetVolumePathNameW (wpath, result, MAX_PATH)) |
||
893 | { |
||
894 | char *msg = g_win32_error_message (GetLastError ()); |
||
895 | g_critical ("GetVolumePathName failed: %s", msg); |
||
896 | g_free (msg); |
||
897 | g_free (result); |
||
898 | g_free (wpath); |
||
899 | return NULL; |
||
900 | } |
||
901 | |||
902 | len = wcslen (result); |
||
903 | if (len > 0 && result[len-1] != L'\\') |
||
904 | { |
||
905 | result = g_renew (wchar_t, result, len + 2); |
||
906 | result[len] = L'\\'; |
||
907 | result[len + 1] = 0; |
||
908 | } |
||
909 | |||
910 | g_free (wpath); |
||
911 | return result; |
||
912 | } |
||
913 | |||
914 | static char * |
||
915 | find_mountpoint_for (const char *file, dev_t dev) |
||
916 | { |
||
917 | wchar_t *wpath; |
||
918 | char *utf8_path; |
||
919 | |||
920 | wpath = get_volume_for_path (file); |
||
921 | if (!wpath) |
||
922 | return NULL; |
||
923 | |||
924 | utf8_path = g_utf16_to_utf8 (wpath, -1, NULL, NULL, NULL); |
||
925 | |||
926 | g_free (wpath); |
||
927 | return utf8_path; |
||
928 | } |
||
929 | |||
930 | static void |
||
931 | get_filesystem_readonly (GFileInfo *info, |
||
932 | const char *path) |
||
933 | { |
||
934 | wchar_t *rootdir; |
||
935 | |||
936 | rootdir = get_volume_for_path (path); |
||
937 | |||
938 | if (rootdir) |
||
939 | { |
||
940 | if (is_xp_or_later ()) |
||
941 | { |
||
942 | DWORD flags; |
||
943 | if (GetVolumeInformationW (rootdir, NULL, 0, NULL, NULL, &flags, NULL, 0)) |
||
944 | g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, |
||
945 | (flags & FILE_READ_ONLY_VOLUME) != 0); |
||
946 | } |
||
947 | else |
||
948 | { |
||
949 | if (GetDriveTypeW (rootdir) == DRIVE_CDROM) |
||
950 | g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); |
||
951 | } |
||
952 | } |
||
953 | |||
954 | g_free (rootdir); |
||
955 | } |
||
956 | |||
957 | #endif /* G_OS_WIN32 */ |
||
958 | |||
959 | static GFileInfo * |
||
960 | g_local_file_query_filesystem_info (GFile *file, |
||
961 | const char *attributes, |
||
962 | GCancellable *cancellable, |
||
963 | GError **error) |
||
964 | { |
||
965 | GLocalFile *local = G_LOCAL_FILE (file); |
||
966 | GFileInfo *info; |
||
967 | int statfs_result = 0; |
||
968 | gboolean no_size; |
||
969 | #ifndef G_OS_WIN32 |
||
970 | const char *fstype; |
||
971 | #ifdef USE_STATFS |
||
972 | guint64 block_size; |
||
973 | struct statfs statfs_buffer; |
||
974 | #elif defined(USE_STATVFS) |
||
975 | guint64 block_size; |
||
976 | struct statvfs statfs_buffer; |
||
977 | #endif /* USE_STATFS */ |
||
978 | #endif /* G_OS_WIN32 */ |
||
979 | GFileAttributeMatcher *attribute_matcher; |
||
980 | |||
981 | no_size = FALSE; |
||
982 | |||
983 | #ifdef USE_STATFS |
||
984 | |||
985 | #if STATFS_ARGS == 2 |
||
986 | statfs_result = statfs (local->filename, &statfs_buffer); |
||
987 | #elif STATFS_ARGS == 4 |
||
988 | statfs_result = statfs (local->filename, &statfs_buffer, |
||
989 | sizeof (statfs_buffer), 0); |
||
990 | #endif /* STATFS_ARGS == 2 */ |
||
991 | block_size = statfs_buffer.f_bsize; |
||
992 | |||
993 | /* Many backends can't report free size (for instance the gvfs fuse |
||
994 | backend for backend not supporting this), and set f_bfree to 0, |
||
995 | but it can be 0 for real too. We treat the available == 0 and |
||
996 | free == 0 case as "both of these are invalid". |
||
997 | */ |
||
998 | #ifndef G_OS_WIN32 |
||
999 | if (statfs_result == 0 && |
||
1000 | statfs_buffer.f_bavail == 0 && statfs_buffer.f_bfree == 0) |
||
1001 | no_size = TRUE; |
||
1002 | #endif /* G_OS_WIN32 */ |
||
1003 | |||
1004 | #elif defined(USE_STATVFS) |
||
1005 | statfs_result = statvfs (local->filename, &statfs_buffer); |
||
1006 | block_size = statfs_buffer.f_frsize; |
||
1007 | #endif /* USE_STATFS */ |
||
1008 | |||
1009 | if (statfs_result == -1) |
||
1010 | { |
||
1011 | int errsv = errno; |
||
1012 | |||
1013 | g_set_error (error, G_IO_ERROR, |
||
1014 | g_io_error_from_errno (errsv), |
||
1015 | _("Error getting filesystem info: %s"), |
||
1016 | g_strerror (errsv)); |
||
1017 | return NULL; |
||
1018 | } |
||
1019 | |||
1020 | info = g_file_info_new (); |
||
1021 | |||
1022 | attribute_matcher = g_file_attribute_matcher_new (attributes); |
||
1023 | |||
1024 | if (!no_size && |
||
1025 | g_file_attribute_matcher_matches (attribute_matcher, |
||
1026 | G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) |
||
1027 | { |
||
1028 | #ifdef G_OS_WIN32 |
||
1029 | gchar *localdir = g_path_get_dirname (local->filename); |
||
1030 | wchar_t *wdirname = g_utf8_to_utf16 (localdir, -1, NULL, NULL, NULL); |
||
1031 | ULARGE_INTEGER li; |
||
1032 | |||
1033 | g_free (localdir); |
||
1034 | if (GetDiskFreeSpaceExW (wdirname, &li, NULL, NULL)) |
||
1035 | g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, (guint64)li.QuadPart); |
||
1036 | g_free (wdirname); |
||
1037 | #else |
||
1038 | #if defined(USE_STATFS) || defined(USE_STATVFS) |
||
1039 | g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, block_size * statfs_buffer.f_bavail); |
||
1040 | #endif |
||
1041 | #endif |
||
1042 | } |
||
1043 | if (!no_size && |
||
1044 | g_file_attribute_matcher_matches (attribute_matcher, |
||
1045 | G_FILE_ATTRIBUTE_FILESYSTEM_SIZE)) |
||
1046 | { |
||
1047 | #ifdef G_OS_WIN32 |
||
1048 | gchar *localdir = g_path_get_dirname (local->filename); |
||
1049 | wchar_t *wdirname = g_utf8_to_utf16 (localdir, -1, NULL, NULL, NULL); |
||
1050 | ULARGE_INTEGER li; |
||
1051 | |||
1052 | g_free (localdir); |
||
1053 | if (GetDiskFreeSpaceExW (wdirname, NULL, &li, NULL)) |
||
1054 | g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, (guint64)li.QuadPart); |
||
1055 | g_free (wdirname); |
||
1056 | #else |
||
1057 | #if defined(USE_STATFS) || defined(USE_STATVFS) |
||
1058 | g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, block_size * statfs_buffer.f_blocks); |
||
1059 | #endif |
||
1060 | #endif /* G_OS_WIN32 */ |
||
1061 | } |
||
1062 | |||
1063 | if (!no_size && |
||
1064 | g_file_attribute_matcher_matches (attribute_matcher, |
||
1065 | G_FILE_ATTRIBUTE_FILESYSTEM_USED)) |
||
1066 | { |
||
1067 | #ifdef G_OS_WIN32 |
||
1068 | gchar *localdir = g_path_get_dirname (local->filename); |
||
1069 | wchar_t *wdirname = g_utf8_to_utf16 (localdir, -1, NULL, NULL, NULL); |
||
1070 | ULARGE_INTEGER li_free; |
||
1071 | ULARGE_INTEGER li_total; |
||
1072 | |||
1073 | g_free (localdir); |
||
1074 | if (GetDiskFreeSpaceExW (wdirname, &li_free, &li_total, NULL)) |
||
1075 | g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, (guint64)li_total.QuadPart - (guint64)li_free.QuadPart); |
||
1076 | g_free (wdirname); |
||
1077 | #else |
||
1078 | g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, block_size * (statfs_buffer.f_blocks - statfs_buffer.f_bfree)); |
||
1079 | #endif /* G_OS_WIN32 */ |
||
1080 | } |
||
1081 | |||
1082 | #ifndef G_OS_WIN32 |
||
1083 | #ifdef USE_STATFS |
||
1084 | #if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) |
||
1085 | fstype = g_strdup (statfs_buffer.f_fstypename); |
||
1086 | #else |
||
1087 | fstype = get_fs_type (statfs_buffer.f_type); |
||
1088 | #endif |
||
1089 | |||
1090 | #elif defined(USE_STATVFS) |
||
1091 | #if defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) |
||
1092 | fstype = g_strdup (statfs_buffer.f_fstypename); |
||
1093 | #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE) |
||
1094 | fstype = g_strdup (statfs_buffer.f_basetype); |
||
1095 | #else |
||
1096 | fstype = NULL; |
||
1097 | #endif |
||
1098 | #endif /* USE_STATFS */ |
||
1099 | |||
1100 | if (fstype && |
||
1101 | g_file_attribute_matcher_matches (attribute_matcher, |
||
1102 | G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)) |
||
1103 | g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, fstype); |
||
1104 | #endif /* G_OS_WIN32 */ |
||
1105 | |||
1106 | if (g_file_attribute_matcher_matches (attribute_matcher, |
||
1107 | G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) |
||
1108 | { |
||
1109 | #ifdef G_OS_WIN32 |
||
1110 | get_filesystem_readonly (info, local->filename); |
||
1111 | #else |
||
1112 | get_mount_info (info, local->filename, attribute_matcher); |
||
1113 | #endif /* G_OS_WIN32 */ |
||
1114 | } |
||
1115 | |||
1116 | g_file_attribute_matcher_unref (attribute_matcher); |
||
1117 | |||
1118 | return info; |
||
1119 | } |
||
1120 | |||
1121 | static GMount * |
||
1122 | g_local_file_find_enclosing_mount (GFile *file, |
||
1123 | GCancellable *cancellable, |
||
1124 | GError **error) |
||
1125 | { |
||
1126 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1127 | GStatBuf buf; |
||
1128 | char *mountpoint; |
||
1129 | GMount *mount; |
||
1130 | |||
1131 | if (g_lstat (local->filename, &buf) != 0) |
||
1132 | { |
||
1133 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, |
||
1134 | /* Translators: This is an error message when trying to |
||
1135 | * find the enclosing (user visible) mount of a file, but |
||
1136 | * none exists. */ |
||
1137 | _("Containing mount does not exist")); |
||
1138 | return NULL; |
||
1139 | } |
||
1140 | |||
1141 | mountpoint = find_mountpoint_for (local->filename, buf.st_dev); |
||
1142 | if (mountpoint == NULL) |
||
1143 | { |
||
1144 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, |
||
1145 | /* Translators: This is an error message when trying to |
||
1146 | * find the enclosing (user visible) mount of a file, but |
||
1147 | * none exists. */ |
||
1148 | _("Containing mount does not exist")); |
||
1149 | return NULL; |
||
1150 | } |
||
1151 | |||
1152 | mount = _g_mount_get_for_mount_path (mountpoint, cancellable); |
||
1153 | g_free (mountpoint); |
||
1154 | if (mount) |
||
1155 | return mount; |
||
1156 | |||
1157 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, |
||
1158 | /* Translators: This is an error message when trying to find |
||
1159 | * the enclosing (user visible) mount of a file, but none |
||
1160 | * exists. */ |
||
1161 | _("Containing mount does not exist")); |
||
1162 | return NULL; |
||
1163 | } |
||
1164 | |||
1165 | static GFile * |
||
1166 | g_local_file_set_display_name (GFile *file, |
||
1167 | const char *display_name, |
||
1168 | GCancellable *cancellable, |
||
1169 | GError **error) |
||
1170 | { |
||
1171 | GLocalFile *local, *new_local; |
||
1172 | GFile *new_file, *parent; |
||
1173 | GStatBuf statbuf; |
||
1174 | GVfsClass *class; |
||
1175 | GVfs *vfs; |
||
1176 | int errsv; |
||
1177 | |||
1178 | parent = g_file_get_parent (file); |
||
1179 | if (parent == NULL) |
||
1180 | { |
||
1181 | g_set_error_literal (error, G_IO_ERROR, |
||
1182 | G_IO_ERROR_FAILED, |
||
1183 | _("Can't rename root directory")); |
||
1184 | return NULL; |
||
1185 | } |
||
1186 | |||
1187 | new_file = g_file_get_child_for_display_name (parent, display_name, error); |
||
1188 | g_object_unref (parent); |
||
1189 | |||
1190 | if (new_file == NULL) |
||
1191 | return NULL; |
||
1192 | local = G_LOCAL_FILE (file); |
||
1193 | new_local = G_LOCAL_FILE (new_file); |
||
1194 | |||
1195 | if (g_lstat (new_local->filename, &statbuf) == -1) |
||
1196 | { |
||
1197 | errsv = errno; |
||
1198 | |||
1199 | if (errsv != ENOENT) |
||
1200 | { |
||
1201 | g_set_error (error, G_IO_ERROR, |
||
1202 | g_io_error_from_errno (errsv), |
||
1203 | _("Error renaming file: %s"), |
||
1204 | g_strerror (errsv)); |
||
1205 | return NULL; |
||
1206 | } |
||
1207 | } |
||
1208 | else |
||
1209 | { |
||
1210 | g_set_error_literal (error, G_IO_ERROR, |
||
1211 | G_IO_ERROR_EXISTS, |
||
1212 | _("Can't rename file, filename already exists")); |
||
1213 | return NULL; |
||
1214 | } |
||
1215 | |||
1216 | if (g_rename (local->filename, new_local->filename) == -1) |
||
1217 | { |
||
1218 | errsv = errno; |
||
1219 | |||
1220 | if (errsv == EINVAL) |
||
1221 | /* We can't get a rename file into itself error herer, |
||
1222 | so this must be an invalid filename, on e.g. FAT */ |
||
1223 | g_set_error_literal (error, G_IO_ERROR, |
||
1224 | G_IO_ERROR_INVALID_FILENAME, |
||
1225 | _("Invalid filename")); |
||
1226 | else |
||
1227 | g_set_error (error, G_IO_ERROR, |
||
1228 | g_io_error_from_errno (errsv), |
||
1229 | _("Error renaming file: %s"), |
||
1230 | g_strerror (errsv)); |
||
1231 | g_object_unref (new_file); |
||
1232 | return NULL; |
||
1233 | } |
||
1234 | |||
1235 | vfs = g_vfs_get_default (); |
||
1236 | class = G_VFS_GET_CLASS (vfs); |
||
1237 | if (class->local_file_moved) |
||
1238 | class->local_file_moved (vfs, local->filename, new_local->filename); |
||
1239 | |||
1240 | return new_file; |
||
1241 | } |
||
1242 | |||
1243 | static GFileInfo * |
||
1244 | g_local_file_query_info (GFile *file, |
||
1245 | const char *attributes, |
||
1246 | GFileQueryInfoFlags flags, |
||
1247 | GCancellable *cancellable, |
||
1248 | GError **error) |
||
1249 | { |
||
1250 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1251 | GFileInfo *info; |
||
1252 | GFileAttributeMatcher *matcher; |
||
1253 | char *basename, *dirname; |
||
1254 | GLocalParentFileInfo parent_info; |
||
1255 | |||
1256 | matcher = g_file_attribute_matcher_new (attributes); |
||
1257 | |||
1258 | basename = g_path_get_basename (local->filename); |
||
1259 | |||
1260 | dirname = g_path_get_dirname (local->filename); |
||
1261 | _g_local_file_info_get_parent_info (dirname, matcher, &parent_info); |
||
1262 | g_free (dirname); |
||
1263 | |||
1264 | info = _g_local_file_info_get (basename, local->filename, |
||
1265 | matcher, flags, &parent_info, |
||
1266 | error); |
||
1267 | |||
1268 | |||
1269 | _g_local_file_info_free_parent_info (&parent_info); |
||
1270 | g_free (basename); |
||
1271 | |||
1272 | g_file_attribute_matcher_unref (matcher); |
||
1273 | |||
1274 | return info; |
||
1275 | } |
||
1276 | |||
1277 | static GFileAttributeInfoList * |
||
1278 | g_local_file_query_settable_attributes (GFile *file, |
||
1279 | GCancellable *cancellable, |
||
1280 | GError **error) |
||
1281 | { |
||
1282 | return g_file_attribute_info_list_ref (local_writable_attributes); |
||
1283 | } |
||
1284 | |||
1285 | static GFileAttributeInfoList * |
||
1286 | g_local_file_query_writable_namespaces (GFile *file, |
||
1287 | GCancellable *cancellable, |
||
1288 | GError **error) |
||
1289 | { |
||
1290 | GFileAttributeInfoList *list; |
||
1291 | GVfsClass *class; |
||
1292 | GVfs *vfs; |
||
1293 | |||
1294 | if (g_once_init_enter (&local_writable_namespaces)) |
||
1295 | { |
||
1296 | /* Writable namespaces: */ |
||
1297 | |||
1298 | list = g_file_attribute_info_list_new (); |
||
1299 | |||
1300 | #ifdef HAVE_XATTR |
||
1301 | g_file_attribute_info_list_add (list, |
||
1302 | "xattr", |
||
1303 | G_FILE_ATTRIBUTE_TYPE_STRING, |
||
1304 | G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | |
||
1305 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
1306 | g_file_attribute_info_list_add (list, |
||
1307 | "xattr-sys", |
||
1308 | G_FILE_ATTRIBUTE_TYPE_STRING, |
||
1309 | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); |
||
1310 | #endif |
||
1311 | |||
1312 | vfs = g_vfs_get_default (); |
||
1313 | class = G_VFS_GET_CLASS (vfs); |
||
1314 | if (class->add_writable_namespaces) |
||
1315 | class->add_writable_namespaces (vfs, list); |
||
1316 | |||
1317 | g_once_init_leave (&local_writable_namespaces, (gsize)list); |
||
1318 | } |
||
1319 | list = (GFileAttributeInfoList *)local_writable_namespaces; |
||
1320 | |||
1321 | return g_file_attribute_info_list_ref (list); |
||
1322 | } |
||
1323 | |||
1324 | static gboolean |
||
1325 | g_local_file_set_attribute (GFile *file, |
||
1326 | const char *attribute, |
||
1327 | GFileAttributeType type, |
||
1328 | gpointer value_p, |
||
1329 | GFileQueryInfoFlags flags, |
||
1330 | GCancellable *cancellable, |
||
1331 | GError **error) |
||
1332 | { |
||
1333 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1334 | |||
1335 | return _g_local_file_info_set_attribute (local->filename, |
||
1336 | attribute, |
||
1337 | type, |
||
1338 | value_p, |
||
1339 | flags, |
||
1340 | cancellable, |
||
1341 | error); |
||
1342 | } |
||
1343 | |||
1344 | static gboolean |
||
1345 | g_local_file_set_attributes_from_info (GFile *file, |
||
1346 | GFileInfo *info, |
||
1347 | GFileQueryInfoFlags flags, |
||
1348 | GCancellable *cancellable, |
||
1349 | GError **error) |
||
1350 | { |
||
1351 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1352 | int res, chained_res; |
||
1353 | GFileIface *default_iface; |
||
1354 | |||
1355 | res = _g_local_file_info_set_attributes (local->filename, |
||
1356 | info, flags, |
||
1357 | cancellable, |
||
1358 | error); |
||
1359 | |||
1360 | if (!res) |
||
1361 | error = NULL; /* Don't write over error if further errors */ |
||
1362 | |||
1363 | default_iface = g_type_default_interface_peek (G_TYPE_FILE); |
||
1364 | |||
1365 | chained_res = (default_iface->set_attributes_from_info) (file, info, flags, cancellable, error); |
||
1366 | |||
1367 | return res && chained_res; |
||
1368 | } |
||
1369 | |||
1370 | static GFileInputStream * |
||
1371 | g_local_file_read (GFile *file, |
||
1372 | GCancellable *cancellable, |
||
1373 | GError **error) |
||
1374 | { |
||
1375 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1376 | int fd, ret; |
||
1377 | GLocalFileStat buf; |
||
1378 | |||
1379 | fd = g_open (local->filename, O_RDONLY|O_BINARY, 0); |
||
1380 | if (fd == -1) |
||
1381 | { |
||
1382 | int errsv = errno; |
||
1383 | |||
1384 | #ifdef G_OS_WIN32 |
||
1385 | if (errsv == EACCES) |
||
1386 | { |
||
1387 | ret = _stati64 (local->filename, &buf); |
||
1388 | if (ret == 0 && S_ISDIR (buf.st_mode)) |
||
1389 | { |
||
1390 | g_set_error_literal (error, G_IO_ERROR, |
||
1391 | G_IO_ERROR_IS_DIRECTORY, |
||
1392 | _("Can't open directory")); |
||
1393 | return NULL; |
||
1394 | } |
||
1395 | } |
||
1396 | #endif |
||
1397 | |||
1398 | g_set_error (error, G_IO_ERROR, |
||
1399 | g_io_error_from_errno (errsv), |
||
1400 | _("Error opening file: %s"), |
||
1401 | g_strerror (errsv)); |
||
1402 | return NULL; |
||
1403 | } |
||
1404 | |||
1405 | #ifdef G_OS_WIN32 |
||
1406 | ret = _fstati64 (fd, &buf); |
||
1407 | #else |
||
1408 | ret = fstat (fd, &buf); |
||
1409 | #endif |
||
1410 | |||
1411 | if (ret == 0 && S_ISDIR (buf.st_mode)) |
||
1412 | { |
||
1413 | (void) g_close (fd, NULL); |
||
1414 | g_set_error_literal (error, G_IO_ERROR, |
||
1415 | G_IO_ERROR_IS_DIRECTORY, |
||
1416 | _("Can't open directory")); |
||
1417 | return NULL; |
||
1418 | } |
||
1419 | |||
1420 | return _g_local_file_input_stream_new (fd); |
||
1421 | } |
||
1422 | |||
1423 | static GFileOutputStream * |
||
1424 | g_local_file_append_to (GFile *file, |
||
1425 | GFileCreateFlags flags, |
||
1426 | GCancellable *cancellable, |
||
1427 | GError **error) |
||
1428 | { |
||
1429 | return _g_local_file_output_stream_append (G_LOCAL_FILE (file)->filename, |
||
1430 | flags, cancellable, error); |
||
1431 | } |
||
1432 | |||
1433 | static GFileOutputStream * |
||
1434 | g_local_file_create (GFile *file, |
||
1435 | GFileCreateFlags flags, |
||
1436 | GCancellable *cancellable, |
||
1437 | GError **error) |
||
1438 | { |
||
1439 | return _g_local_file_output_stream_create (G_LOCAL_FILE (file)->filename, |
||
1440 | FALSE, flags, NULL, |
||
1441 | cancellable, error); |
||
1442 | } |
||
1443 | |||
1444 | static GFileOutputStream * |
||
1445 | g_local_file_replace (GFile *file, |
||
1446 | const char *etag, |
||
1447 | gboolean make_backup, |
||
1448 | GFileCreateFlags flags, |
||
1449 | GCancellable *cancellable, |
||
1450 | GError **error) |
||
1451 | { |
||
1452 | return _g_local_file_output_stream_replace (G_LOCAL_FILE (file)->filename, |
||
1453 | FALSE, |
||
1454 | etag, make_backup, flags, NULL, |
||
1455 | cancellable, error); |
||
1456 | } |
||
1457 | |||
1458 | static GFileIOStream * |
||
1459 | g_local_file_open_readwrite (GFile *file, |
||
1460 | GCancellable *cancellable, |
||
1461 | GError **error) |
||
1462 | { |
||
1463 | GFileOutputStream *output; |
||
1464 | GFileIOStream *res; |
||
1465 | |||
1466 | output = _g_local_file_output_stream_open (G_LOCAL_FILE (file)->filename, |
||
1467 | TRUE, |
||
1468 | cancellable, error); |
||
1469 | if (output == NULL) |
||
1470 | return NULL; |
||
1471 | |||
1472 | res = _g_local_file_io_stream_new (G_LOCAL_FILE_OUTPUT_STREAM (output)); |
||
1473 | g_object_unref (output); |
||
1474 | return res; |
||
1475 | } |
||
1476 | |||
1477 | static GFileIOStream * |
||
1478 | g_local_file_create_readwrite (GFile *file, |
||
1479 | GFileCreateFlags flags, |
||
1480 | GCancellable *cancellable, |
||
1481 | GError **error) |
||
1482 | { |
||
1483 | GFileOutputStream *output; |
||
1484 | GFileIOStream *res; |
||
1485 | |||
1486 | output = _g_local_file_output_stream_create (G_LOCAL_FILE (file)->filename, |
||
1487 | TRUE, flags, NULL, |
||
1488 | cancellable, error); |
||
1489 | if (output == NULL) |
||
1490 | return NULL; |
||
1491 | |||
1492 | res = _g_local_file_io_stream_new (G_LOCAL_FILE_OUTPUT_STREAM (output)); |
||
1493 | g_object_unref (output); |
||
1494 | return res; |
||
1495 | } |
||
1496 | |||
1497 | static GFileIOStream * |
||
1498 | g_local_file_replace_readwrite (GFile *file, |
||
1499 | const char *etag, |
||
1500 | gboolean make_backup, |
||
1501 | GFileCreateFlags flags, |
||
1502 | GCancellable *cancellable, |
||
1503 | GError **error) |
||
1504 | { |
||
1505 | GFileOutputStream *output; |
||
1506 | GFileIOStream *res; |
||
1507 | |||
1508 | output = _g_local_file_output_stream_replace (G_LOCAL_FILE (file)->filename, |
||
1509 | TRUE, |
||
1510 | etag, make_backup, flags, NULL, |
||
1511 | cancellable, error); |
||
1512 | if (output == NULL) |
||
1513 | return NULL; |
||
1514 | |||
1515 | res = _g_local_file_io_stream_new (G_LOCAL_FILE_OUTPUT_STREAM (output)); |
||
1516 | g_object_unref (output); |
||
1517 | return res; |
||
1518 | } |
||
1519 | |||
1520 | static gboolean |
||
1521 | g_local_file_delete (GFile *file, |
||
1522 | GCancellable *cancellable, |
||
1523 | GError **error) |
||
1524 | { |
||
1525 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1526 | GVfsClass *class; |
||
1527 | GVfs *vfs; |
||
1528 | |||
1529 | if (g_remove (local->filename) == -1) |
||
1530 | { |
||
1531 | int errsv = errno; |
||
1532 | |||
1533 | /* Posix allows EEXIST too, but the more sane error |
||
1534 | is G_IO_ERROR_NOT_FOUND, and it's what nautilus |
||
1535 | expects */ |
||
1536 | if (errsv == EEXIST) |
||
1537 | errsv = ENOTEMPTY; |
||
1538 | |||
1539 | g_set_error (error, G_IO_ERROR, |
||
1540 | g_io_error_from_errno (errsv), |
||
1541 | _("Error removing file: %s"), |
||
1542 | g_strerror (errsv)); |
||
1543 | return FALSE; |
||
1544 | } |
||
1545 | |||
1546 | vfs = g_vfs_get_default (); |
||
1547 | class = G_VFS_GET_CLASS (vfs); |
||
1548 | if (class->local_file_removed) |
||
1549 | class->local_file_removed (vfs, local->filename); |
||
1550 | |||
1551 | return TRUE; |
||
1552 | } |
||
1553 | |||
1554 | #ifndef G_OS_WIN32 |
||
1555 | |||
1556 | static char * |
||
1557 | strip_trailing_slashes (const char *path) |
||
1558 | { |
||
1559 | char *path_copy; |
||
1560 | int len; |
||
1561 | |||
1562 | path_copy = g_strdup (path); |
||
1563 | len = strlen (path_copy); |
||
1564 | while (len > 1 && path_copy[len-1] == '/') |
||
1565 | path_copy[--len] = 0; |
||
1566 | |||
1567 | return path_copy; |
||
1568 | } |
||
1569 | |||
1570 | static char * |
||
1571 | expand_symlink (const char *link) |
||
1572 | { |
||
1573 | char *resolved, *canonical, *parent, *link2; |
||
1574 | char symlink_value[4096]; |
||
1575 | #ifdef G_OS_WIN32 |
||
1576 | #else |
||
1577 | ssize_t res; |
||
1578 | #endif |
||
1579 | |||
1580 | #ifdef G_OS_WIN32 |
||
1581 | #else |
||
1582 | res = readlink (link, symlink_value, sizeof (symlink_value) - 1); |
||
1583 | |||
1584 | if (res == -1) |
||
1585 | return g_strdup (link); |
||
1586 | symlink_value[res] = 0; |
||
1587 | #endif |
||
1588 | |||
1589 | if (g_path_is_absolute (symlink_value)) |
||
1590 | return canonicalize_filename (symlink_value); |
||
1591 | else |
||
1592 | { |
||
1593 | link2 = strip_trailing_slashes (link); |
||
1594 | parent = g_path_get_dirname (link2); |
||
1595 | g_free (link2); |
||
1596 | |||
1597 | resolved = g_build_filename (parent, symlink_value, NULL); |
||
1598 | g_free (parent); |
||
1599 | |||
1600 | canonical = canonicalize_filename (resolved); |
||
1601 | |||
1602 | g_free (resolved); |
||
1603 | |||
1604 | return canonical; |
||
1605 | } |
||
1606 | } |
||
1607 | |||
1608 | static char * |
||
1609 | get_parent (const char *path, |
||
1610 | dev_t *parent_dev) |
||
1611 | { |
||
1612 | char *parent, *tmp; |
||
1613 | GStatBuf parent_stat; |
||
1614 | int num_recursions; |
||
1615 | char *path_copy; |
||
1616 | |||
1617 | path_copy = strip_trailing_slashes (path); |
||
1618 | |||
1619 | parent = g_path_get_dirname (path_copy); |
||
1620 | if (strcmp (parent, ".") == 0 || |
||
1621 | strcmp (parent, path_copy) == 0) |
||
1622 | { |
||
1623 | g_free (parent); |
||
1624 | g_free (path_copy); |
||
1625 | return NULL; |
||
1626 | } |
||
1627 | g_free (path_copy); |
||
1628 | |||
1629 | num_recursions = 0; |
||
1630 | do { |
||
1631 | if (g_lstat (parent, &parent_stat) != 0) |
||
1632 | { |
||
1633 | g_free (parent); |
||
1634 | return NULL; |
||
1635 | } |
||
1636 | |||
1637 | if (S_ISLNK (parent_stat.st_mode)) |
||
1638 | { |
||
1639 | tmp = parent; |
||
1640 | parent = expand_symlink (parent); |
||
1641 | g_free (tmp); |
||
1642 | } |
||
1643 | |||
1644 | num_recursions++; |
||
1645 | if (num_recursions > 12) |
||
1646 | { |
||
1647 | g_free (parent); |
||
1648 | return NULL; |
||
1649 | } |
||
1650 | } while (S_ISLNK (parent_stat.st_mode)); |
||
1651 | |||
1652 | *parent_dev = parent_stat.st_dev; |
||
1653 | |||
1654 | return parent; |
||
1655 | } |
||
1656 | |||
1657 | static char * |
||
1658 | expand_all_symlinks (const char *path) |
||
1659 | { |
||
1660 | char *parent, *parent_expanded; |
||
1661 | char *basename, *res; |
||
1662 | dev_t parent_dev; |
||
1663 | |||
1664 | parent = get_parent (path, &parent_dev); |
||
1665 | if (parent) |
||
1666 | { |
||
1667 | parent_expanded = expand_all_symlinks (parent); |
||
1668 | g_free (parent); |
||
1669 | basename = g_path_get_basename (path); |
||
1670 | res = g_build_filename (parent_expanded, basename, NULL); |
||
1671 | g_free (basename); |
||
1672 | g_free (parent_expanded); |
||
1673 | } |
||
1674 | else |
||
1675 | res = g_strdup (path); |
||
1676 | |||
1677 | return res; |
||
1678 | } |
||
1679 | |||
1680 | static char * |
||
1681 | find_mountpoint_for (const char *file, |
||
1682 | dev_t dev) |
||
1683 | { |
||
1684 | char *dir, *parent; |
||
1685 | dev_t dir_dev, parent_dev; |
||
1686 | |||
1687 | dir = g_strdup (file); |
||
1688 | dir_dev = dev; |
||
1689 | |||
1690 | while (1) |
||
1691 | { |
||
1692 | parent = get_parent (dir, &parent_dev); |
||
1693 | if (parent == NULL) |
||
1694 | return dir; |
||
1695 | |||
1696 | if (parent_dev != dir_dev) |
||
1697 | { |
||
1698 | g_free (parent); |
||
1699 | return dir; |
||
1700 | } |
||
1701 | |||
1702 | g_free (dir); |
||
1703 | dir = parent; |
||
1704 | } |
||
1705 | } |
||
1706 | |||
1707 | static char * |
||
1708 | find_topdir_for (const char *file) |
||
1709 | { |
||
1710 | char *dir; |
||
1711 | char *mountpoint = NULL; |
||
1712 | dev_t dir_dev; |
||
1713 | |||
1714 | dir = get_parent (file, &dir_dev); |
||
1715 | if (dir == NULL) |
||
1716 | return NULL; |
||
1717 | |||
1718 | mountpoint = find_mountpoint_for (dir, dir_dev); |
||
1719 | g_free (dir); |
||
1720 | |||
1721 | return mountpoint; |
||
1722 | } |
||
1723 | |||
1724 | static char * |
||
1725 | get_unique_filename (const char *basename, |
||
1726 | int id) |
||
1727 | { |
||
1728 | const char *dot; |
||
1729 | |||
1730 | if (id == 1) |
||
1731 | return g_strdup (basename); |
||
1732 | |||
1733 | dot = strchr (basename, '.'); |
||
1734 | if (dot) |
||
1735 | return g_strdup_printf ("%.*s.%d%s", (int)(dot - basename), basename, id, dot); |
||
1736 | else |
||
1737 | return g_strdup_printf ("%s.%d", basename, id); |
||
1738 | } |
||
1739 | |||
1740 | static gboolean |
||
1741 | path_has_prefix (const char *path, |
||
1742 | const char *prefix) |
||
1743 | { |
||
1744 | int prefix_len; |
||
1745 | |||
1746 | if (prefix == NULL) |
||
1747 | return TRUE; |
||
1748 | |||
1749 | prefix_len = strlen (prefix); |
||
1750 | |||
1751 | if (strncmp (path, prefix, prefix_len) == 0 && |
||
1752 | (prefix_len == 0 || /* empty prefix always matches */ |
||
1753 | prefix[prefix_len - 1] == '/' || /* last char in prefix was a /, so it must be in path too */ |
||
1754 | path[prefix_len] == 0 || |
||
1755 | path[prefix_len] == '/')) |
||
1756 | return TRUE; |
||
1757 | |||
1758 | return FALSE; |
||
1759 | } |
||
1760 | |||
1761 | static char * |
||
1762 | try_make_relative (const char *path, |
||
1763 | const char *base) |
||
1764 | { |
||
1765 | char *path2, *base2; |
||
1766 | char *relative; |
||
1767 | |||
1768 | path2 = expand_all_symlinks (path); |
||
1769 | base2 = expand_all_symlinks (base); |
||
1770 | |||
1771 | relative = NULL; |
||
1772 | if (path_has_prefix (path2, base2)) |
||
1773 | { |
||
1774 | relative = path2 + strlen (base2); |
||
1775 | while (*relative == '/') |
||
1776 | relative ++; |
||
1777 | relative = g_strdup (relative); |
||
1778 | } |
||
1779 | g_free (path2); |
||
1780 | g_free (base2); |
||
1781 | |||
1782 | if (relative) |
||
1783 | return relative; |
||
1784 | |||
1785 | /* Failed, use abs path */ |
||
1786 | return g_strdup (path); |
||
1787 | } |
||
1788 | |||
1789 | gboolean |
||
1790 | _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev) |
||
1791 | { |
||
1792 | static gsize home_dev_set = 0; |
||
1793 | static dev_t home_dev; |
||
1794 | char *topdir, *globaldir, *trashdir, *tmpname; |
||
1795 | uid_t uid; |
||
1796 | char uid_str[32]; |
||
1797 | GStatBuf global_stat, trash_stat; |
||
1798 | gboolean res; |
||
1799 | |||
1800 | if (g_once_init_enter (&home_dev_set)) |
||
1801 | { |
||
1802 | GStatBuf home_stat; |
||
1803 | |||
1804 | g_stat (g_get_home_dir (), &home_stat); |
||
1805 | home_dev = home_stat.st_dev; |
||
1806 | g_once_init_leave (&home_dev_set, 1); |
||
1807 | } |
||
1808 | |||
1809 | /* Assume we can trash to the home */ |
||
1810 | if (dir_dev == home_dev) |
||
1811 | return TRUE; |
||
1812 | |||
1813 | topdir = find_mountpoint_for (dirname, dir_dev); |
||
1814 | if (topdir == NULL) |
||
1815 | return FALSE; |
||
1816 | |||
1817 | globaldir = g_build_filename (topdir, ".Trash", NULL); |
||
1818 | if (g_lstat (globaldir, &global_stat) == 0 && |
||
1819 | S_ISDIR (global_stat.st_mode) && |
||
1820 | (global_stat.st_mode & S_ISVTX) != 0) |
||
1821 | { |
||
1822 | /* got a toplevel sysadmin created dir, assume we |
||
1823 | * can trash to it (we should be able to create a dir) |
||
1824 | * This fails for the FAT case where the ownership of |
||
1825 | * that dir would be wrong though.. |
||
1826 | */ |
||
1827 | g_free (globaldir); |
||
1828 | g_free (topdir); |
||
1829 | return TRUE; |
||
1830 | } |
||
1831 | g_free (globaldir); |
||
1832 | |||
1833 | /* No global trash dir, or it failed the tests, fall back to $topdir/.Trash-$uid */ |
||
1834 | uid = geteuid (); |
||
1835 | g_snprintf (uid_str, sizeof (uid_str), "%lu", (unsigned long) uid); |
||
1836 | |||
1837 | tmpname = g_strdup_printf (".Trash-%s", uid_str); |
||
1838 | trashdir = g_build_filename (topdir, tmpname, NULL); |
||
1839 | g_free (tmpname); |
||
1840 | |||
1841 | if (g_lstat (trashdir, &trash_stat) == 0) |
||
1842 | { |
||
1843 | g_free (topdir); |
||
1844 | g_free (trashdir); |
||
1845 | return S_ISDIR (trash_stat.st_mode) && |
||
1846 | trash_stat.st_uid == uid; |
||
1847 | } |
||
1848 | g_free (trashdir); |
||
1849 | |||
1850 | /* User specific trash didn't exist, can we create it? */ |
||
1851 | res = g_access (topdir, W_OK) == 0; |
||
1852 | g_free (topdir); |
||
1853 | |||
1854 | return res; |
||
1855 | } |
||
1856 | |||
1857 | #ifdef G_OS_UNIX |
||
1858 | gboolean |
||
1859 | _g_local_file_is_lost_found_dir (const char *path, dev_t path_dev) |
||
1860 | { |
||
1861 | gboolean ret = FALSE; |
||
1862 | gchar *mount_dir = NULL; |
||
1863 | size_t mount_dir_len; |
||
1864 | GStatBuf statbuf; |
||
1865 | |||
1866 | if (!g_str_has_suffix (path, "/lost+found")) |
||
1867 | goto out; |
||
1868 | |||
1869 | mount_dir = find_mountpoint_for (path, path_dev); |
||
1870 | if (mount_dir == NULL) |
||
1871 | goto out; |
||
1872 | |||
1873 | mount_dir_len = strlen (mount_dir); |
||
1874 | /* We special-case rootfs ('/') since it's the only case where |
||
1875 | * mount_dir ends in '/' |
||
1876 | */ |
||
1877 | if (mount_dir_len == 1) |
||
1878 | mount_dir_len--; |
||
1879 | if (mount_dir_len + strlen ("/lost+found") != strlen (path)) |
||
1880 | goto out; |
||
1881 | |||
1882 | if (g_lstat (path, &statbuf) != 0) |
||
1883 | goto out; |
||
1884 | |||
1885 | if (!(S_ISDIR (statbuf.st_mode) && |
||
1886 | statbuf.st_uid == 0 && |
||
1887 | statbuf.st_gid == 0)) |
||
1888 | goto out; |
||
1889 | |||
1890 | ret = TRUE; |
||
1891 | |||
1892 | out: |
||
1893 | g_free (mount_dir); |
||
1894 | return ret; |
||
1895 | } |
||
1896 | #endif |
||
1897 | |||
1898 | static gboolean |
||
1899 | g_local_file_trash (GFile *file, |
||
1900 | GCancellable *cancellable, |
||
1901 | GError **error) |
||
1902 | { |
||
1903 | GLocalFile *local = G_LOCAL_FILE (file); |
||
1904 | GStatBuf file_stat, home_stat; |
||
1905 | const char *homedir; |
||
1906 | char *trashdir, *topdir, *infodir, *filesdir; |
||
1907 | char *basename, *trashname, *trashfile, *infoname, *infofile; |
||
1908 | char *original_name, *original_name_escaped; |
||
1909 | int i; |
||
1910 | char *data; |
||
1911 | gboolean is_homedir_trash; |
||
1912 | char delete_time[32]; |
||
1913 | int fd; |
||
1914 | GStatBuf trash_stat, global_stat; |
||
1915 | char *dirname, *globaldir; |
||
1916 | GVfsClass *class; |
||
1917 | GVfs *vfs; |
||
1918 | |||
1919 | if (g_lstat (local->filename, &file_stat) != 0) |
||
1920 | { |
||
1921 | int errsv = errno; |
||
1922 | |||
1923 | g_set_error (error, G_IO_ERROR, |
||
1924 | g_io_error_from_errno (errsv), |
||
1925 | _("Error trashing file: %s"), |
||
1926 | g_strerror (errsv)); |
||
1927 | return FALSE; |
||
1928 | } |
||
1929 | |||
1930 | homedir = g_get_home_dir (); |
||
1931 | g_stat (homedir, &home_stat); |
||
1932 | |||
1933 | is_homedir_trash = FALSE; |
||
1934 | trashdir = NULL; |
||
1935 | if (file_stat.st_dev == home_stat.st_dev) |
||
1936 | { |
||
1937 | is_homedir_trash = TRUE; |
||
1938 | errno = 0; |
||
1939 | trashdir = g_build_filename (g_get_user_data_dir (), "Trash", NULL); |
||
1940 | if (g_mkdir_with_parents (trashdir, 0700) < 0) |
||
1941 | { |
||
1942 | char *display_name; |
||
1943 | int errsv = errno; |
||
1944 | |||
1945 | display_name = g_filename_display_name (trashdir); |
||
1946 | g_set_error (error, G_IO_ERROR, |
||
1947 | g_io_error_from_errno (errsv), |
||
1948 | _("Unable to create trash dir %s: %s"), |
||
1949 | display_name, g_strerror (errsv)); |
||
1950 | g_free (display_name); |
||
1951 | g_free (trashdir); |
||
1952 | return FALSE; |
||
1953 | } |
||
1954 | topdir = g_strdup (g_get_user_data_dir ()); |
||
1955 | } |
||
1956 | else |
||
1957 | { |
||
1958 | uid_t uid; |
||
1959 | char uid_str[32]; |
||
1960 | |||
1961 | uid = geteuid (); |
||
1962 | g_snprintf (uid_str, sizeof (uid_str), "%lu", (unsigned long)uid); |
||
1963 | |||
1964 | topdir = find_topdir_for (local->filename); |
||
1965 | if (topdir == NULL) |
||
1966 | { |
||
1967 | g_set_error_literal (error, G_IO_ERROR, |
||
1968 | G_IO_ERROR_NOT_SUPPORTED, |
||
1969 | _("Unable to find toplevel directory for trash")); |
||
1970 | return FALSE; |
||
1971 | } |
||
1972 | |||
1973 | /* Try looking for global trash dir $topdir/.Trash/$uid */ |
||
1974 | globaldir = g_build_filename (topdir, ".Trash", NULL); |
||
1975 | if (g_lstat (globaldir, &global_stat) == 0 && |
||
1976 | S_ISDIR (global_stat.st_mode) && |
||
1977 | (global_stat.st_mode & S_ISVTX) != 0) |
||
1978 | { |
||
1979 | trashdir = g_build_filename (globaldir, uid_str, NULL); |
||
1980 | |||
1981 | if (g_lstat (trashdir, &trash_stat) == 0) |
||
1982 | { |
||
1983 | if (!S_ISDIR (trash_stat.st_mode) || |
||
1984 | trash_stat.st_uid != uid) |
||
1985 | { |
||
1986 | /* Not a directory or not owned by user, ignore */ |
||
1987 | g_free (trashdir); |
||
1988 | trashdir = NULL; |
||
1989 | } |
||
1990 | } |
||
1991 | else if (g_mkdir (trashdir, 0700) == -1) |
||
1992 | { |
||
1993 | g_free (trashdir); |
||
1994 | trashdir = NULL; |
||
1995 | } |
||
1996 | } |
||
1997 | g_free (globaldir); |
||
1998 | |||
1999 | if (trashdir == NULL) |
||
2000 | { |
||
2001 | gboolean tried_create; |
||
2002 | |||
2003 | /* No global trash dir, or it failed the tests, fall back to $topdir/.Trash-$uid */ |
||
2004 | dirname = g_strdup_printf (".Trash-%s", uid_str); |
||
2005 | trashdir = g_build_filename (topdir, dirname, NULL); |
||
2006 | g_free (dirname); |
||
2007 | |||
2008 | tried_create = FALSE; |
||
2009 | |||
2010 | retry: |
||
2011 | if (g_lstat (trashdir, &trash_stat) == 0) |
||
2012 | { |
||
2013 | if (!S_ISDIR (trash_stat.st_mode) || |
||
2014 | trash_stat.st_uid != uid) |
||
2015 | { |
||
2016 | /* Remove the failed directory */ |
||
2017 | if (tried_create) |
||
2018 | g_remove (trashdir); |
||
2019 | |||
2020 | /* Not a directory or not owned by user, ignore */ |
||
2021 | g_free (trashdir); |
||
2022 | trashdir = NULL; |
||
2023 | } |
||
2024 | } |
||
2025 | else |
||
2026 | { |
||
2027 | if (!tried_create && |
||
2028 | g_mkdir (trashdir, 0700) != -1) |
||
2029 | { |
||
2030 | /* Ensure that the created dir has the right uid etc. |
||
2031 | This might fail on e.g. a FAT dir */ |
||
2032 | tried_create = TRUE; |
||
2033 | goto retry; |
||
2034 | } |
||
2035 | else |
||
2036 | { |
||
2037 | g_free (trashdir); |
||
2038 | trashdir = NULL; |
||
2039 | } |
||
2040 | } |
||
2041 | } |
||
2042 | |||
2043 | if (trashdir == NULL) |
||
2044 | { |
||
2045 | g_free (topdir); |
||
2046 | g_set_error_literal (error, G_IO_ERROR, |
||
2047 | G_IO_ERROR_NOT_SUPPORTED, |
||
2048 | _("Unable to find or create trash directory")); |
||
2049 | return FALSE; |
||
2050 | } |
||
2051 | } |
||
2052 | |||
2053 | /* Trashdir points to the trash dir with the "info" and "files" subdirectories */ |
||
2054 | |||
2055 | infodir = g_build_filename (trashdir, "info", NULL); |
||
2056 | filesdir = g_build_filename (trashdir, "files", NULL); |
||
2057 | g_free (trashdir); |
||
2058 | |||
2059 | /* Make sure we have the subdirectories */ |
||
2060 | if ((g_mkdir (infodir, 0700) == -1 && errno != EEXIST) || |
||
2061 | (g_mkdir (filesdir, 0700) == -1 && errno != EEXIST)) |
||
2062 | { |
||
2063 | g_free (topdir); |
||
2064 | g_free (infodir); |
||
2065 | g_free (filesdir); |
||
2066 | g_set_error_literal (error, G_IO_ERROR, |
||
2067 | G_IO_ERROR_NOT_SUPPORTED, |
||
2068 | _("Unable to find or create trash directory")); |
||
2069 | return FALSE; |
||
2070 | } |
||
2071 | |||
2072 | basename = g_path_get_basename (local->filename); |
||
2073 | i = 1; |
||
2074 | trashname = NULL; |
||
2075 | infofile = NULL; |
||
2076 | do { |
||
2077 | g_free (trashname); |
||
2078 | g_free (infofile); |
||
2079 | |||
2080 | trashname = get_unique_filename (basename, i++); |
||
2081 | infoname = g_strconcat (trashname, ".trashinfo", NULL); |
||
2082 | infofile = g_build_filename (infodir, infoname, NULL); |
||
2083 | g_free (infoname); |
||
2084 | |||
2085 | fd = g_open (infofile, O_CREAT | O_EXCL, 0666); |
||
2086 | } while (fd == -1 && errno == EEXIST); |
||
2087 | |||
2088 | g_free (basename); |
||
2089 | g_free (infodir); |
||
2090 | |||
2091 | if (fd == -1) |
||
2092 | { |
||
2093 | int errsv = errno; |
||
2094 | |||
2095 | g_free (filesdir); |
||
2096 | g_free (topdir); |
||
2097 | g_free (trashname); |
||
2098 | g_free (infofile); |
||
2099 | |||
2100 | g_set_error (error, G_IO_ERROR, |
||
2101 | g_io_error_from_errno (errsv), |
||
2102 | _("Unable to create trashing info file: %s"), |
||
2103 | g_strerror (errsv)); |
||
2104 | return FALSE; |
||
2105 | } |
||
2106 | |||
2107 | (void) g_close (fd, NULL); |
||
2108 | |||
2109 | /* Write the full content of the info file before trashing to make |
||
2110 | * sure someone doesn't read an empty file. See #749314 |
||
2111 | */ |
||
2112 | |||
2113 | /* Use absolute names for homedir */ |
||
2114 | if (is_homedir_trash) |
||
2115 | original_name = g_strdup (local->filename); |
||
2116 | else |
||
2117 | original_name = try_make_relative (local->filename, topdir); |
||
2118 | original_name_escaped = g_uri_escape_string (original_name, "/", FALSE); |
||
2119 | |||
2120 | g_free (original_name); |
||
2121 | g_free (topdir); |
||
2122 | |||
2123 | { |
||
2124 | time_t t; |
||
2125 | struct tm now; |
||
2126 | t = time (NULL); |
||
2127 | localtime_r (&t, &now); |
||
2128 | delete_time[0] = 0; |
||
2129 | strftime(delete_time, sizeof (delete_time), "%Y-%m-%dT%H:%M:%S", &now); |
||
2130 | } |
||
2131 | |||
2132 | data = g_strdup_printf ("[Trash Info]\nPath=%s\nDeletionDate=%s\n", |
||
2133 | original_name_escaped, delete_time); |
||
2134 | |||
2135 | g_file_set_contents (infofile, data, -1, NULL); |
||
2136 | |||
2137 | /* TODO: Maybe we should verify that you can delete the file from the trash |
||
2138 | before moving it? OTOH, that is hard, as it needs a recursive scan */ |
||
2139 | |||
2140 | trashfile = g_build_filename (filesdir, trashname, NULL); |
||
2141 | |||
2142 | g_free (filesdir); |
||
2143 | |||
2144 | if (g_rename (local->filename, trashfile) == -1) |
||
2145 | { |
||
2146 | int errsv = errno; |
||
2147 | |||
2148 | g_unlink (infofile); |
||
2149 | |||
2150 | g_free (trashname); |
||
2151 | g_free (infofile); |
||
2152 | g_free (trashfile); |
||
2153 | |||
2154 | if (errsv == EXDEV) |
||
2155 | /* The trash dir was actually on another fs anyway!? |
||
2156 | This can happen when the same device is mounted multiple |
||
2157 | times, or with bind mounts of the same fs. */ |
||
2158 | g_set_error (error, G_IO_ERROR, |
||
2159 | G_IO_ERROR_NOT_SUPPORTED, |
||
2160 | _("Unable to trash file: %s"), |
||
2161 | g_strerror (errsv)); |
||
2162 | else |
||
2163 | g_set_error (error, G_IO_ERROR, |
||
2164 | g_io_error_from_errno (errsv), |
||
2165 | _("Unable to trash file: %s"), |
||
2166 | g_strerror (errsv)); |
||
2167 | return FALSE; |
||
2168 | } |
||
2169 | |||
2170 | vfs = g_vfs_get_default (); |
||
2171 | class = G_VFS_GET_CLASS (vfs); |
||
2172 | if (class->local_file_moved) |
||
2173 | class->local_file_moved (vfs, local->filename, trashfile); |
||
2174 | |||
2175 | g_free (trashfile); |
||
2176 | |||
2177 | /* TODO: Do we need to update mtime/atime here after the move? */ |
||
2178 | |||
2179 | g_free (infofile); |
||
2180 | g_free (data); |
||
2181 | |||
2182 | g_free (original_name_escaped); |
||
2183 | g_free (trashname); |
||
2184 | |||
2185 | return TRUE; |
||
2186 | } |
||
2187 | #else /* G_OS_WIN32 */ |
||
2188 | gboolean |
||
2189 | _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev) |
||
2190 | { |
||
2191 | return FALSE; /* XXX ??? */ |
||
2192 | } |
||
2193 | |||
2194 | static gboolean |
||
2195 | g_local_file_trash (GFile *file, |
||
2196 | GCancellable *cancellable, |
||
2197 | GError **error) |
||
2198 | { |
||
2199 | GLocalFile *local = G_LOCAL_FILE (file); |
||
2200 | SHFILEOPSTRUCTW op = {0}; |
||
2201 | gboolean success; |
||
2202 | wchar_t *wfilename; |
||
2203 | long len; |
||
2204 | |||
2205 | wfilename = g_utf8_to_utf16 (local->filename, -1, NULL, &len, NULL); |
||
2206 | /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */ |
||
2207 | wfilename = g_renew (wchar_t, wfilename, len + 2); |
||
2208 | wfilename[len + 1] = 0; |
||
2209 | |||
2210 | op.wFunc = FO_DELETE; |
||
2211 | op.pFrom = wfilename; |
||
2212 | op.fFlags = FOF_ALLOWUNDO; |
||
2213 | |||
2214 | success = SHFileOperationW (&op) == 0; |
||
2215 | |||
2216 | if (success && op.fAnyOperationsAborted) |
||
2217 | { |
||
2218 | if (cancellable && !g_cancellable_is_cancelled (cancellable)) |
||
2219 | g_cancellable_cancel (cancellable); |
||
2220 | g_set_error (error, G_IO_ERROR, |
||
2221 | G_IO_ERROR_CANCELLED, |
||
2222 | _("Unable to trash file: %s"), |
||
2223 | _("Operation was cancelled")); |
||
2224 | success = FALSE; |
||
2225 | } |
||
2226 | else if (!success) |
||
2227 | g_set_error (error, G_IO_ERROR, |
||
2228 | G_IO_ERROR_FAILED, |
||
2229 | _("Unable to trash file: %s"), |
||
2230 | _("internal error")); |
||
2231 | |||
2232 | g_free (wfilename); |
||
2233 | return success; |
||
2234 | } |
||
2235 | #endif /* G_OS_WIN32 */ |
||
2236 | |||
2237 | static gboolean |
||
2238 | g_local_file_make_directory (GFile *file, |
||
2239 | GCancellable *cancellable, |
||
2240 | GError **error) |
||
2241 | { |
||
2242 | GLocalFile *local = G_LOCAL_FILE (file); |
||
2243 | |||
2244 | if (g_mkdir (local->filename, 0777) == -1) |
||
2245 | { |
||
2246 | int errsv = errno; |
||
2247 | |||
2248 | if (errsv == EINVAL) |
||
2249 | /* This must be an invalid filename, on e.g. FAT */ |
||
2250 | g_set_error_literal (error, G_IO_ERROR, |
||
2251 | G_IO_ERROR_INVALID_FILENAME, |
||
2252 | _("Invalid filename")); |
||
2253 | else |
||
2254 | g_set_error (error, G_IO_ERROR, |
||
2255 | g_io_error_from_errno (errsv), |
||
2256 | _("Error creating directory: %s"), |
||
2257 | g_strerror (errsv)); |
||
2258 | return FALSE; |
||
2259 | } |
||
2260 | |||
2261 | return TRUE; |
||
2262 | } |
||
2263 | |||
2264 | static gboolean |
||
2265 | g_local_file_make_symbolic_link (GFile *file, |
||
2266 | const char *symlink_value, |
||
2267 | GCancellable *cancellable, |
||
2268 | GError **error) |
||
2269 | { |
||
2270 | #ifdef HAVE_SYMLINK |
||
2271 | GLocalFile *local = G_LOCAL_FILE (file); |
||
2272 | |||
2273 | if (symlink (symlink_value, local->filename) == -1) |
||
2274 | { |
||
2275 | int errsv = errno; |
||
2276 | |||
2277 | if (errsv == EINVAL) |
||
2278 | /* This must be an invalid filename, on e.g. FAT */ |
||
2279 | g_set_error_literal (error, G_IO_ERROR, |
||
2280 | G_IO_ERROR_INVALID_FILENAME, |
||
2281 | _("Invalid filename")); |
||
2282 | else if (errsv == EPERM) |
||
2283 | g_set_error (error, G_IO_ERROR, |
||
2284 | G_IO_ERROR_NOT_SUPPORTED, |
||
2285 | _("Filesystem does not support symbolic links")); |
||
2286 | else |
||
2287 | g_set_error (error, G_IO_ERROR, |
||
2288 | g_io_error_from_errno (errsv), |
||
2289 | _("Error making symbolic link: %s"), |
||
2290 | g_strerror (errsv)); |
||
2291 | return FALSE; |
||
2292 | } |
||
2293 | return TRUE; |
||
2294 | #else |
||
2295 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Symlinks not supported"); |
||
2296 | return FALSE; |
||
2297 | #endif |
||
2298 | } |
||
2299 | |||
2300 | |||
2301 | static gboolean |
||
2302 | g_local_file_copy (GFile *source, |
||
2303 | GFile *destination, |
||
2304 | GFileCopyFlags flags, |
||
2305 | GCancellable *cancellable, |
||
2306 | GFileProgressCallback progress_callback, |
||
2307 | gpointer progress_callback_data, |
||
2308 | GError **error) |
||
2309 | { |
||
2310 | /* Fall back to default copy */ |
||
2311 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported"); |
||
2312 | return FALSE; |
||
2313 | } |
||
2314 | |||
2315 | static gboolean |
||
2316 | g_local_file_move (GFile *source, |
||
2317 | GFile *destination, |
||
2318 | GFileCopyFlags flags, |
||
2319 | GCancellable *cancellable, |
||
2320 | GFileProgressCallback progress_callback, |
||
2321 | gpointer progress_callback_data, |
||
2322 | GError **error) |
||
2323 | { |
||
2324 | GLocalFile *local_source, *local_destination; |
||
2325 | GStatBuf statbuf; |
||
2326 | gboolean destination_exist, source_is_dir; |
||
2327 | char *backup_name; |
||
2328 | int res; |
||
2329 | off_t source_size; |
||
2330 | GVfsClass *class; |
||
2331 | GVfs *vfs; |
||
2332 | |||
2333 | if (!G_IS_LOCAL_FILE (source) || |
||
2334 | !G_IS_LOCAL_FILE (destination)) |
||
2335 | { |
||
2336 | /* Fall back to default move */ |
||
2337 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Move not supported"); |
||
2338 | return FALSE; |
||
2339 | } |
||
2340 | |||
2341 | local_source = G_LOCAL_FILE (source); |
||
2342 | local_destination = G_LOCAL_FILE (destination); |
||
2343 | |||
2344 | res = g_lstat (local_source->filename, &statbuf); |
||
2345 | if (res == -1) |
||
2346 | { |
||
2347 | int errsv = errno; |
||
2348 | |||
2349 | g_set_error (error, G_IO_ERROR, |
||
2350 | g_io_error_from_errno (errsv), |
||
2351 | _("Error moving file: %s"), |
||
2352 | g_strerror (errsv)); |
||
2353 | return FALSE; |
||
2354 | } |
||
2355 | |||
2356 | source_is_dir = S_ISDIR (statbuf.st_mode); |
||
2357 | source_size = statbuf.st_size; |
||
2358 | |||
2359 | destination_exist = FALSE; |
||
2360 | res = g_lstat (local_destination->filename, &statbuf); |
||
2361 | if (res == 0) |
||
2362 | { |
||
2363 | destination_exist = TRUE; /* Target file exists */ |
||
2364 | |||
2365 | if (flags & G_FILE_COPY_OVERWRITE) |
||
2366 | { |
||
2367 | /* Always fail on dirs, even with overwrite */ |
||
2368 | if (S_ISDIR (statbuf.st_mode)) |
||
2369 | { |
||
2370 | if (source_is_dir) |
||
2371 | g_set_error_literal (error, |
||
2372 | G_IO_ERROR, |
||
2373 | G_IO_ERROR_WOULD_MERGE, |
||
2374 | _("Can't move directory over directory")); |
||
2375 | else |
||
2376 | g_set_error_literal (error, |
||
2377 | G_IO_ERROR, |
||
2378 | G_IO_ERROR_IS_DIRECTORY, |
||
2379 | _("Can't copy over directory")); |
||
2380 | return FALSE; |
||
2381 | } |
||
2382 | } |
||
2383 | else |
||
2384 | { |
||
2385 | g_set_error_literal (error, |
||
2386 | G_IO_ERROR, |
||
2387 | G_IO_ERROR_EXISTS, |
||
2388 | _("Target file exists")); |
||
2389 | return FALSE; |
||
2390 | } |
||
2391 | } |
||
2392 | |||
2393 | if (flags & G_FILE_COPY_BACKUP && destination_exist) |
||
2394 | { |
||
2395 | backup_name = g_strconcat (local_destination->filename, "~", NULL); |
||
2396 | if (g_rename (local_destination->filename, backup_name) == -1) |
||
2397 | { |
||
2398 | g_set_error_literal (error, |
||
2399 | G_IO_ERROR, |
||
2400 | G_IO_ERROR_CANT_CREATE_BACKUP, |
||
2401 | _("Backup file creation failed")); |
||
2402 | g_free (backup_name); |
||
2403 | return FALSE; |
||
2404 | } |
||
2405 | g_free (backup_name); |
||
2406 | destination_exist = FALSE; /* It did, but no more */ |
||
2407 | } |
||
2408 | |||
2409 | if (source_is_dir && destination_exist && (flags & G_FILE_COPY_OVERWRITE)) |
||
2410 | { |
||
2411 | /* Source is a dir, destination exists (and is not a dir, because that would have failed |
||
2412 | earlier), and we're overwriting. Manually remove the target so we can do the rename. */ |
||
2413 | res = g_unlink (local_destination->filename); |
||
2414 | if (res == -1) |
||
2415 | { |
||
2416 | int errsv = errno; |
||
2417 | |||
2418 | g_set_error (error, G_IO_ERROR, |
||
2419 | g_io_error_from_errno (errsv), |
||
2420 | _("Error removing target file: %s"), |
||
2421 | g_strerror (errsv)); |
||
2422 | return FALSE; |
||
2423 | } |
||
2424 | } |
||
2425 | |||
2426 | if (g_rename (local_source->filename, local_destination->filename) == -1) |
||
2427 | { |
||
2428 | int errsv = errno; |
||
2429 | |||
2430 | if (errsv == EXDEV) |
||
2431 | /* This will cause the fallback code to run */ |
||
2432 | g_set_error_literal (error, G_IO_ERROR, |
||
2433 | G_IO_ERROR_NOT_SUPPORTED, |
||
2434 | _("Move between mounts not supported")); |
||
2435 | else if (errsv == EINVAL) |
||
2436 | /* This must be an invalid filename, on e.g. FAT, or |
||
2437 | we're trying to move the file into itself... |
||
2438 | We return invalid filename for both... */ |
||
2439 | g_set_error_literal (error, G_IO_ERROR, |
||
2440 | G_IO_ERROR_INVALID_FILENAME, |
||
2441 | _("Invalid filename")); |
||
2442 | else |
||
2443 | g_set_error (error, G_IO_ERROR, |
||
2444 | g_io_error_from_errno (errsv), |
||
2445 | _("Error moving file: %s"), |
||
2446 | g_strerror (errsv)); |
||
2447 | return FALSE; |
||
2448 | } |
||
2449 | |||
2450 | vfs = g_vfs_get_default (); |
||
2451 | class = G_VFS_GET_CLASS (vfs); |
||
2452 | if (class->local_file_moved) |
||
2453 | class->local_file_moved (vfs, local_source->filename, local_destination->filename); |
||
2454 | |||
2455 | /* Make sure we send full copied size */ |
||
2456 | if (progress_callback) |
||
2457 | progress_callback (source_size, source_size, progress_callback_data); |
||
2458 | |||
2459 | return TRUE; |
||
2460 | } |
||
2461 | |||
2462 | #ifdef G_OS_WIN32 |
||
2463 | |||
2464 | gboolean |
||
2465 | g_local_file_is_remote (const gchar *filename) |
||
2466 | { |
||
2467 | return FALSE; |
||
2468 | } |
||
2469 | |||
2470 | #else |
||
2471 | |||
2472 | static gboolean |
||
2473 | is_remote_fs (const gchar *filename) |
||
2474 | { |
||
2475 | const char *fsname = NULL; |
||
2476 | |||
2477 | #ifdef USE_STATFS |
||
2478 | struct statfs statfs_buffer; |
||
2479 | int statfs_result = 0; |
||
2480 | |||
2481 | #if STATFS_ARGS == 2 |
||
2482 | statfs_result = statfs (filename, &statfs_buffer); |
||
2483 | #elif STATFS_ARGS == 4 |
||
2484 | statfs_result = statfs (filename, &statfs_buffer, sizeof (statfs_buffer), 0); |
||
2485 | #endif |
||
2486 | |||
2487 | #elif defined(USE_STATVFS) |
||
2488 | struct statvfs statfs_buffer; |
||
2489 | int statfs_result = 0; |
||
2490 | |||
2491 | statfs_result = statvfs (filename, &statfs_buffer); |
||
2492 | #else |
||
2493 | return FALSE; |
||
2494 | #endif |
||
2495 | |||
2496 | if (statfs_result == -1) |
||
2497 | return FALSE; |
||
2498 | |||
2499 | #ifdef USE_STATFS |
||
2500 | #if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) |
||
2501 | fsname = statfs_buffer.f_fstypename; |
||
2502 | #else |
||
2503 | fsname = get_fs_type (statfs_buffer.f_type); |
||
2504 | #endif |
||
2505 | |||
2506 | #elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE) |
||
2507 | fsname = statfs_buffer.f_basetype; |
||
2508 | #endif |
||
2509 | |||
2510 | if (fsname != NULL) |
||
2511 | { |
||
2512 | if (strcmp (fsname, "nfs") == 0) |
||
2513 | return TRUE; |
||
2514 | if (strcmp (fsname, "nfs4") == 0) |
||
2515 | return TRUE; |
||
2516 | } |
||
2517 | |||
2518 | return FALSE; |
||
2519 | } |
||
2520 | |||
2521 | gboolean |
||
2522 | g_local_file_is_remote (const gchar *filename) |
||
2523 | { |
||
2524 | static gboolean remote_home; |
||
2525 | static gsize initialized; |
||
2526 | const gchar *home; |
||
2527 | |||
2528 | home = g_get_home_dir (); |
||
2529 | if (path_has_prefix (filename, home)) |
||
2530 | { |
||
2531 | if (g_once_init_enter (&initialized)) |
||
2532 | { |
||
2533 | remote_home = is_remote_fs (home); |
||
2534 | g_once_init_leave (&initialized, TRUE); |
||
2535 | } |
||
2536 | return remote_home; |
||
2537 | } |
||
2538 | |||
2539 | return FALSE; |
||
2540 | } |
||
2541 | #endif /* !G_OS_WIN32 */ |
||
2542 | |||
2543 | static GFileMonitor* |
||
2544 | g_local_file_monitor_dir (GFile *file, |
||
2545 | GFileMonitorFlags flags, |
||
2546 | GCancellable *cancellable, |
||
2547 | GError **error) |
||
2548 | { |
||
2549 | GLocalFile *local_file = G_LOCAL_FILE (file); |
||
2550 | |||
2551 | return g_local_file_monitor_new_for_path (local_file->filename, TRUE, flags, error); |
||
2552 | } |
||
2553 | |||
2554 | static GFileMonitor* |
||
2555 | g_local_file_monitor_file (GFile *file, |
||
2556 | GFileMonitorFlags flags, |
||
2557 | GCancellable *cancellable, |
||
2558 | GError **error) |
||
2559 | { |
||
2560 | GLocalFile *local_file = G_LOCAL_FILE (file); |
||
2561 | |||
2562 | return g_local_file_monitor_new_for_path (local_file->filename, FALSE, flags, error); |
||
2563 | } |
||
2564 | |||
2565 | /* Here is the GLocalFile implementation of g_file_measure_disk_usage(). |
||
2566 | * |
||
2567 | * If available, we use fopenat() in preference to filenames for |
||
2568 | * efficiency and safety reasons. We know that fopenat() is available |
||
2569 | * based on if AT_FDCWD is defined. POSIX guarantees that this will be |
||
2570 | * defined as a macro. |
||
2571 | * |
||
2572 | * We use a linked list of stack-allocated GSList nodes in order to be |
||
2573 | * able to reconstruct the filename for error messages. We actually |
||
2574 | * pass the filename to operate on through the top node of the list. |
||
2575 | * |
||
2576 | * In case we're using openat(), this top filename will be a basename |
||
2577 | * which should be opened in the directory which has also had its fd |
||
2578 | * passed along. If we're not using openat() then it will be a full |
||
2579 | * absolute filename. |
||
2580 | */ |
||
2581 | |||
2582 | static gboolean |
||
2583 | g_local_file_measure_size_error (GFileMeasureFlags flags, |
||
2584 | gint saved_errno, |
||
2585 | GSList *name, |
||
2586 | GError **error) |
||
2587 | { |
||
2588 | /* Only report an error if we were at the toplevel or if the caller |
||
2589 | * requested reporting of all errors. |
||
2590 | */ |
||
2591 | if ((name->next == NULL) || (flags & G_FILE_MEASURE_REPORT_ANY_ERROR)) |
||
2592 | { |
||
2593 | GString *filename; |
||
2594 | GSList *node; |
||
2595 | |||
2596 | /* Skip some work if there is no error return */ |
||
2597 | if (!error) |
||
2598 | return FALSE; |
||
2599 | |||
2600 | #ifdef AT_FDCWD |
||
2601 | /* If using openat() we need to rebuild the filename for the message */ |
||
2602 | filename = g_string_new (name->data); |
||
2603 | for (node = name->next; node; node = node->next) |
||
2604 | { |
||
2605 | gchar *utf8; |
||
2606 | |||
2607 | g_string_prepend_c (filename, G_DIR_SEPARATOR); |
||
2608 | utf8 = g_filename_display_name (node->data); |
||
2609 | g_string_prepend (filename, utf8); |
||
2610 | g_free (utf8); |
||
2611 | } |
||
2612 | #else |
||
2613 | { |
||
2614 | gchar *utf8; |
||
2615 | |||
2616 | /* Otherwise, we already have it, so just use it. */ |
||
2617 | node = name; |
||
2618 | filename = g_string_new (NULL); |
||
2619 | utf8 = g_filename_display_name (node->data); |
||
2620 | g_string_append (filename, utf8); |
||
2621 | g_free (utf8); |
||
2622 | } |
||
2623 | #endif |
||
2624 | |||
2625 | g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), |
||
2626 | _("Could not determine the disk usage of %s: %s"), |
||
2627 | filename->str, g_strerror (saved_errno)); |
||
2628 | |||
2629 | g_string_free (filename, TRUE); |
||
2630 | |||
2631 | return FALSE; |
||
2632 | } |
||
2633 | |||
2634 | else |
||
2635 | /* We're not reporting this error... */ |
||
2636 | return TRUE; |
||
2637 | } |
||
2638 | |||
2639 | typedef struct |
||
2640 | { |
||
2641 | GFileMeasureFlags flags; |
||
2642 | dev_t contained_on; |
||
2643 | GCancellable *cancellable; |
||
2644 | |||
2645 | GFileMeasureProgressCallback progress_callback; |
||
2646 | gpointer progress_data; |
||
2647 | |||
2648 | guint64 disk_usage; |
||
2649 | guint64 num_dirs; |
||
2650 | guint64 num_files; |
||
2651 | |||
2652 | guint64 last_progress_report; |
||
2653 | } MeasureState; |
||
2654 | |||
2655 | static gboolean |
||
2656 | g_local_file_measure_size_of_contents (gint fd, |
||
2657 | GSList *dir_name, |
||
2658 | MeasureState *state, |
||
2659 | GError **error); |
||
2660 | |||
2661 | static gboolean |
||
2662 | g_local_file_measure_size_of_file (gint parent_fd, |
||
2663 | GSList *name, |
||
2664 | MeasureState *state, |
||
2665 | GError **error) |
||
2666 | { |
||
2667 | GLocalFileStat buf; |
||
2668 | |||
2669 | if (g_cancellable_set_error_if_cancelled (state->cancellable, error)) |
||
2670 | return FALSE; |
||
2671 | |||
2672 | #if defined (AT_FDCWD) |
||
2673 | if (fstatat (parent_fd, name->data, &buf, AT_SYMLINK_NOFOLLOW) != 0) |
||
2674 | return g_local_file_measure_size_error (state->flags, errno, name, error); |
||
2675 | #elif defined (HAVE_LSTAT) || !defined (G_OS_WIN32) |
||
2676 | if (g_lstat (name->data, &buf) != 0) |
||
2677 | return g_local_file_measure_size_error (state->flags, errno, name, error); |
||
2678 | #else |
||
2679 | { |
||
2680 | const char *filename = (const gchar *) name->data; |
||
2681 | wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); |
||
2682 | int retval; |
||
2683 | int save_errno; |
||
2684 | int len; |
||
2685 | |||
2686 | if (wfilename == NULL) |
||
2687 | return g_local_file_measure_size_error (state->flags, errno, name, error); |
||
2688 | |||
2689 | len = wcslen (wfilename); |
||
2690 | while (len > 0 && G_IS_DIR_SEPARATOR (wfilename[len-1])) |
||
2691 | len--; |
||
2692 | if (len > 0 && |
||
2693 | (!g_path_is_absolute (filename) || len > g_path_skip_root (filename) - filename)) |
||
2694 | wfilename[len] = '\0'; |
||
2695 | |||
2696 | retval = _wstati64 (wfilename, &buf); |
||
2697 | save_errno = errno; |
||
2698 | |||
2699 | g_free (wfilename); |
||
2700 | |||
2701 | errno = save_errno; |
||
2702 | if (retval != 0) |
||
2703 | return g_local_file_measure_size_error (state->flags, errno, name, error); |
||
2704 | } |
||
2705 | #endif |
||
2706 | |||
2707 | if (name->next) |
||
2708 | { |
||
2709 | /* If not at the toplevel, check for a device boundary. */ |
||
2710 | |||
2711 | if (state->flags & G_FILE_MEASURE_NO_XDEV) |
||
2712 | if (state->contained_on != buf.st_dev) |
||
2713 | return TRUE; |
||
2714 | } |
||
2715 | else |
||
2716 | { |
||
2717 | /* If, however, this is the toplevel, set the device number so |
||
2718 | * that recursive invocations can compare against it. |
||
2719 | */ |
||
2720 | state->contained_on = buf.st_dev; |
||
2721 | } |
||
2722 | |||
2723 | #if defined (HAVE_STRUCT_STAT_ST_BLOCKS) |
||
2724 | if (~state->flags & G_FILE_MEASURE_APPARENT_SIZE) |
||
2725 | state->disk_usage += buf.st_blocks * G_GUINT64_CONSTANT (512); |
||
2726 | else |
||
2727 | #endif |
||
2728 | state->disk_usage += buf.st_size; |
||
2729 | |||
2730 | if (S_ISDIR (buf.st_mode)) |
||
2731 | state->num_dirs++; |
||
2732 | else |
||
2733 | state->num_files++; |
||
2734 | |||
2735 | if (state->progress_callback) |
||
2736 | { |
||
2737 | /* We could attempt to do some cleverness here in order to avoid |
||
2738 | * calling clock_gettime() so much, but we're doing stats and opens |
||
2739 | * all over the place already... |
||
2740 | */ |
||
2741 | if (state->last_progress_report) |
||
2742 | { |
||
2743 | guint64 now; |
||
2744 | |||
2745 | now = g_get_monotonic_time (); |
||
2746 | |||
2747 | if (state->last_progress_report + 200 * G_TIME_SPAN_MILLISECOND < now) |
||
2748 | { |
||
2749 | (* state->progress_callback) (TRUE, |
||
2750 | state->disk_usage, state->num_dirs, state->num_files, |
||
2751 | state->progress_data); |
||
2752 | state->last_progress_report = now; |
||
2753 | } |
||
2754 | } |
||
2755 | else |
||
2756 | { |
||
2757 | /* We must do an initial report to inform that more reports |
||
2758 | * will be coming. |
||
2759 | */ |
||
2760 | (* state->progress_callback) (TRUE, 0, 0, 0, state->progress_data); |
||
2761 | state->last_progress_report = g_get_monotonic_time (); |
||
2762 | } |
||
2763 | } |
||
2764 | |||
2765 | if (S_ISDIR (buf.st_mode)) |
||
2766 | { |
||
2767 | int dir_fd = -1; |
||
2768 | |||
2769 | if (g_cancellable_set_error_if_cancelled (state->cancellable, error)) |
||
2770 | return FALSE; |
||
2771 | |||
2772 | #ifdef AT_FDCWD |
||
2773 | #ifdef HAVE_OPEN_O_DIRECTORY |
||
2774 | dir_fd = openat (parent_fd, name->data, O_RDONLY|O_DIRECTORY); |
||
2775 | #else |
||
2776 | dir_fd = openat (parent_fd, name->data, O_RDONLY); |
||
2777 | #endif |
||
2778 | if (dir_fd < 0) |
||
2779 | return g_local_file_measure_size_error (state->flags, errno, name, error); |
||
2780 | #endif |
||
2781 | |||
2782 | if (!g_local_file_measure_size_of_contents (dir_fd, name, state, error)) |
||
2783 | return FALSE; |
||
2784 | } |
||
2785 | |||
2786 | return TRUE; |
||
2787 | } |
||
2788 | |||
2789 | static gboolean |
||
2790 | g_local_file_measure_size_of_contents (gint fd, |
||
2791 | GSList *dir_name, |
||
2792 | MeasureState *state, |
||
2793 | GError **error) |
||
2794 | { |
||
2795 | gboolean success = TRUE; |
||
2796 | const gchar *name; |
||
2797 | GDir *dir; |
||
2798 | |||
2799 | #ifdef AT_FDCWD |
||
2800 | { |
||
2801 | /* If this fails, we want to preserve the errno from fopendir() */ |
||
2802 | DIR *dirp; |
||
2803 | dirp = fdopendir (fd); |
||
2804 | dir = dirp ? GLIB_PRIVATE_CALL(g_dir_new_from_dirp) (dirp) : NULL; |
||
2805 | } |
||
2806 | #else |
||
2807 | dir = GLIB_PRIVATE_CALL(g_dir_open_with_errno) (dir_name->data, 0); |
||
2808 | #endif |
||
2809 | |||
2810 | if (dir == NULL) |
||
2811 | { |
||
2812 | gint saved_errno = errno; |
||
2813 | |||
2814 | #ifdef AT_FDCWD |
||
2815 | close (fd); |
||
2816 | #endif |
||
2817 | |||
2818 | return g_local_file_measure_size_error (state->flags, saved_errno, dir_name, error); |
||
2819 | } |
||
2820 | |||
2821 | while (success && (name = g_dir_read_name (dir))) |
||
2822 | { |
||
2823 | GSList node; |
||
2824 | |||
2825 | node.next = dir_name; |
||
2826 | #ifdef AT_FDCWD |
||
2827 | node.data = (gchar *) name; |
||
2828 | #else |
||
2829 | node.data = g_build_filename (dir_name->data, name, NULL); |
||
2830 | #endif |
||
2831 | |||
2832 | success = g_local_file_measure_size_of_file (fd, &node, state, error); |
||
2833 | |||
2834 | #ifndef AT_FDCWD |
||
2835 | g_free (node.data); |
||
2836 | #endif |
||
2837 | } |
||
2838 | |||
2839 | g_dir_close (dir); |
||
2840 | |||
2841 | return success; |
||
2842 | } |
||
2843 | |||
2844 | static gboolean |
||
2845 | g_local_file_measure_disk_usage (GFile *file, |
||
2846 | GFileMeasureFlags flags, |
||
2847 | GCancellable *cancellable, |
||
2848 | GFileMeasureProgressCallback progress_callback, |
||
2849 | gpointer progress_data, |
||
2850 | guint64 *disk_usage, |
||
2851 | guint64 *num_dirs, |
||
2852 | guint64 *num_files, |
||
2853 | GError **error) |
||
2854 | { |
||
2855 | GLocalFile *local_file = G_LOCAL_FILE (file); |
||
2856 | MeasureState state = { 0, }; |
||
2857 | gint root_fd = -1; |
||
2858 | GSList node; |
||
2859 | |||
2860 | state.flags = flags; |
||
2861 | state.cancellable = cancellable; |
||
2862 | state.progress_callback = progress_callback; |
||
2863 | state.progress_data = progress_data; |
||
2864 | |||
2865 | #ifdef AT_FDCWD |
||
2866 | root_fd = AT_FDCWD; |
||
2867 | #endif |
||
2868 | |||
2869 | node.data = local_file->filename; |
||
2870 | node.next = NULL; |
||
2871 | |||
2872 | if (!g_local_file_measure_size_of_file (root_fd, &node, &state, error)) |
||
2873 | return FALSE; |
||
2874 | |||
2875 | if (disk_usage) |
||
2876 | *disk_usage = state.disk_usage; |
||
2877 | |||
2878 | if (num_dirs) |
||
2879 | *num_dirs = state.num_dirs; |
||
2880 | |||
2881 | if (num_files) |
||
2882 | *num_files = state.num_files; |
||
2883 | |||
2884 | return TRUE; |
||
2885 | } |
||
2886 | |||
2887 | static void |
||
2888 | g_local_file_file_iface_init (GFileIface *iface) |
||
2889 | { |
||
2890 | iface->dup = g_local_file_dup; |
||
2891 | iface->hash = g_local_file_hash; |
||
2892 | iface->equal = g_local_file_equal; |
||
2893 | iface->is_native = g_local_file_is_native; |
||
2894 | iface->has_uri_scheme = g_local_file_has_uri_scheme; |
||
2895 | iface->get_uri_scheme = g_local_file_get_uri_scheme; |
||
2896 | iface->get_basename = g_local_file_get_basename; |
||
2897 | iface->get_path = g_local_file_get_path; |
||
2898 | iface->get_uri = g_local_file_get_uri; |
||
2899 | iface->get_parse_name = g_local_file_get_parse_name; |
||
2900 | iface->get_parent = g_local_file_get_parent; |
||
2901 | iface->prefix_matches = g_local_file_prefix_matches; |
||
2902 | iface->get_relative_path = g_local_file_get_relative_path; |
||
2903 | iface->resolve_relative_path = g_local_file_resolve_relative_path; |
||
2904 | iface->get_child_for_display_name = g_local_file_get_child_for_display_name; |
||
2905 | iface->set_display_name = g_local_file_set_display_name; |
||
2906 | iface->enumerate_children = g_local_file_enumerate_children; |
||
2907 | iface->query_info = g_local_file_query_info; |
||
2908 | iface->query_filesystem_info = g_local_file_query_filesystem_info; |
||
2909 | iface->find_enclosing_mount = g_local_file_find_enclosing_mount; |
||
2910 | iface->query_settable_attributes = g_local_file_query_settable_attributes; |
||
2911 | iface->query_writable_namespaces = g_local_file_query_writable_namespaces; |
||
2912 | iface->set_attribute = g_local_file_set_attribute; |
||
2913 | iface->set_attributes_from_info = g_local_file_set_attributes_from_info; |
||
2914 | iface->read_fn = g_local_file_read; |
||
2915 | iface->append_to = g_local_file_append_to; |
||
2916 | iface->create = g_local_file_create; |
||
2917 | iface->replace = g_local_file_replace; |
||
2918 | iface->open_readwrite = g_local_file_open_readwrite; |
||
2919 | iface->create_readwrite = g_local_file_create_readwrite; |
||
2920 | iface->replace_readwrite = g_local_file_replace_readwrite; |
||
2921 | iface->delete_file = g_local_file_delete; |
||
2922 | iface->trash = g_local_file_trash; |
||
2923 | iface->make_directory = g_local_file_make_directory; |
||
2924 | iface->make_symbolic_link = g_local_file_make_symbolic_link; |
||
2925 | iface->copy = g_local_file_copy; |
||
2926 | iface->move = g_local_file_move; |
||
2927 | iface->monitor_dir = g_local_file_monitor_dir; |
||
2928 | iface->monitor_file = g_local_file_monitor_file; |
||
2929 | iface->measure_disk_usage = g_local_file_measure_disk_usage; |
||
2930 | |||
2931 | iface->supports_thread_contexts = TRUE; |
||
2932 | } |