dirent.h is a conditional include now.
[wine] / dlls / shell32 / trash.c
1 /*
2  * The freedesktop.org Trash, implemented using the 0.7 spec version
3  * (see http://www.ramendik.ru/docs/trashspec.html)
4  *
5  * Copyright (C) 2006 Mikolaj Zalewski
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23
24 #include <stdarg.h>
25 #ifdef HAVE_SYS_STAT_H
26 # include <sys/stat.h>
27 #endif
28 #include <sys/types.h>
29 #include <stdlib.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33 #ifdef HAVE_DIRENT_H
34 # include <dirent.h>
35 #endif
36
37 #include "windef.h"
38 #include "winbase.h"
39 #include "winerror.h"
40 #include "winreg.h"
41 #include "shlwapi.h"
42 #include "winternl.h"
43
44 #include <stdio.h>
45 #include <fcntl.h>
46 #include <errno.h>
47 #include <time.h>
48 #include "wine/debug.h"
49 #include "shell32_main.h"
50 #include "xdg.h"
51
52 WINE_DEFAULT_DEBUG_CHANNEL(trash);
53
54 static CRITICAL_SECTION TRASH_Creating;
55 static CRITICAL_SECTION_DEBUG TRASH_Creating_Debug =
56 {
57     0, 0, &TRASH_Creating,
58     { &TRASH_Creating_Debug.ProcessLocksList,
59       &TRASH_Creating_Debug.ProcessLocksList},
60     0, 0, { (DWORD_PTR)__FILE__ ": TRASH_Creating"}
61 };
62 static CRITICAL_SECTION TRASH_Creating = { &TRASH_Creating_Debug, -1, 0, 0, 0, 0 };
63
64 static const char trashinfo_suffix[] = ".trashinfo";
65 static const char trashinfo_header[] = "[Trash Info]\n";
66 static const char trashinfo_group[] = "Trash Info";
67
68 typedef struct
69 {
70     char *info_dir;
71     char *files_dir;
72     dev_t device;
73 } TRASH_BUCKET;
74
75 static TRASH_BUCKET *home_trash=NULL;
76
77 static char *init_home_dir(const char *subpath)
78 {
79     char *path = XDG_BuildPath(XDG_DATA_HOME, subpath);
80     if (path == NULL) return NULL;
81     if (!XDG_MakeDirs(path))
82     {
83         ERR("Couldn't create directory %s (errno=%d). Trash won't be available\n", debugstr_a(path), errno);
84         SHFree(path);
85         path=NULL;
86     }
87     return path;
88 }
89
90 static TRASH_BUCKET *TRASH_CreateHomeBucket(void)
91 {
92     TRASH_BUCKET *bucket;
93     struct stat trash_stat;
94     char *trash_path = NULL;
95     
96     bucket = SHAlloc(sizeof(TRASH_BUCKET));
97     if (bucket == NULL)
98     {
99         errno = ENOMEM;
100         goto error;
101     }
102     memset(bucket, 0, sizeof(*bucket));
103     bucket->info_dir = init_home_dir("Trash/info/");
104     if (bucket->info_dir == NULL) goto error;
105     bucket->files_dir = init_home_dir("Trash/files/");
106     if (bucket->files_dir == NULL) goto error;
107     
108     trash_path = XDG_BuildPath(XDG_DATA_HOME, "Trash/");
109     if (stat(trash_path, &trash_stat) == -1)
110         goto error;
111     bucket->device = trash_stat.st_dev;
112     SHFree(trash_path);
113     return bucket;
114 error:
115     SHFree(trash_path);
116     if (bucket)
117     {
118         SHFree(bucket->info_dir);
119         SHFree(bucket->files_dir);
120     }
121     SHFree(bucket);
122     return NULL;
123 }
124
125 static BOOL TRASH_EnsureInitialized(void)
126 {
127     if (home_trash == NULL)
128     {
129         EnterCriticalSection(&TRASH_Creating);
130         if (home_trash == NULL)
131             home_trash = TRASH_CreateHomeBucket();
132         LeaveCriticalSection(&TRASH_Creating);
133     }
134
135     if (home_trash == NULL)
136     {
137         ERR("Couldn't initialize home trash (errno=%d)\n", errno);
138         return FALSE;
139     }
140     return TRUE;
141 }
142
143 static BOOL file_good_for_bucket(const TRASH_BUCKET *pBucket, const struct stat *file_stat)
144 {
145     if (pBucket->device != file_stat->st_dev)
146         return FALSE;
147     return TRUE;
148 }
149
150 BOOL TRASH_CanTrashFile(LPCWSTR wszPath)
151 {
152     struct stat file_stat;
153     char *unix_path;
154     
155     TRACE("(%s)\n", debugstr_w(wszPath));
156     if (!TRASH_EnsureInitialized()) return FALSE;
157     if (!(unix_path = wine_get_unix_file_name(wszPath)))
158         return FALSE;
159     if (lstat(unix_path, &file_stat)==-1)
160     {
161         HeapFree(GetProcessHeap(), 0, unix_path);
162         return FALSE;
163     }
164     HeapFree(GetProcessHeap(), 0, unix_path);
165     return file_good_for_bucket(home_trash, &file_stat);
166 }
167
168 /*
169  * Try to create a single .trashinfo file. Return TRUE if successful, else FALSE
170  */
171 static BOOL try_create_trashinfo_file(const char *info_dir, const char *file_name,
172     const char *original_file_name)
173 {
174     struct tm curr_time;
175     time_t curr_time_secs;
176     char datebuf[200];
177     char *path = SHAlloc(strlen(info_dir)+strlen(file_name)+strlen(trashinfo_suffix)+1);
178     int writer = -1;
179     
180     if (path==NULL) return FALSE;
181     wsprintfA(path, "%s%s%s", info_dir, file_name, trashinfo_suffix);
182     TRACE("Trying to create '%s'\n", path);
183     writer = open(path, O_CREAT|O_WRONLY|O_TRUNC|O_EXCL, 0600);
184     if (writer==-1) goto error;
185     
186     write(writer, trashinfo_header, strlen(trashinfo_header));
187     if (!XDG_WriteDesktopStringEntry(writer, "Path", XDG_URLENCODE, original_file_name))
188         goto error;
189     
190     time(&curr_time_secs);
191     localtime_r(&curr_time_secs, &curr_time);
192     wnsprintfA(datebuf, 200, "%04d-%02d-%02dT%02d:%02d:%02d",
193         curr_time.tm_year+1900,
194         curr_time.tm_mon+1,
195         curr_time.tm_mday,
196         curr_time.tm_hour,
197         curr_time.tm_min,
198         curr_time.tm_sec);
199     if (!XDG_WriteDesktopStringEntry(writer, "DeletionDate", 0, datebuf))
200         goto error;
201     close(writer);
202     SHFree(path);
203     return TRUE;
204
205 error:
206     if (writer != -1)
207     {
208         close(writer);
209         unlink(path);
210     }
211     SHFree(path);
212     return FALSE;
213 }
214
215 /*
216  * Try to create a .trashinfo file. This function will make several attempts with
217  * different filenames. It will return the filename that succeded or NULL if a file
218  * couldn't be created.
219  */
220 static char *create_trashinfo(const char *info_dir, const char *file_path)
221 {
222     const char *base_name;
223     char *filename_buffer;
224     unsigned int seed = (unsigned int)time(NULL);
225     int i;
226
227     errno = ENOMEM;       /* out-of-memory is the only case when errno isn't set */
228     base_name = strrchr(file_path, '/');
229     if (base_name == NULL)
230         base_name = file_path;
231     else
232         base_name++;
233
234     filename_buffer = SHAlloc(strlen(base_name)+9+1);
235     if (filename_buffer == NULL)
236         return NULL;
237     lstrcpyA(filename_buffer, base_name);
238     if (try_create_trashinfo_file(info_dir, filename_buffer, file_path))
239         return filename_buffer;
240     for (i=0; i<30; i++)
241     {
242         sprintf(filename_buffer, "%s-%d", base_name, i+1);
243         if (try_create_trashinfo_file(info_dir, filename_buffer, file_path))
244             return filename_buffer;
245     }
246     
247     for (i=0; i<1000; i++)
248     {
249         sprintf(filename_buffer, "%s-%08x", base_name, rand_r(&seed));
250         if (try_create_trashinfo_file(info_dir, filename_buffer, file_path))
251             return filename_buffer;
252     }
253     
254     WARN("Couldn't create trashinfo after 1031 tries (errno=%d)\n", errno);
255     SHFree(filename_buffer);
256     return NULL;
257 }
258
259 static void remove_trashinfo_file(const char *info_dir, const char *base_name)
260 {
261     char *filename_buffer;
262     
263     filename_buffer = SHAlloc(lstrlenA(info_dir)+lstrlenA(base_name)+lstrlenA(trashinfo_suffix)+1);
264     if (filename_buffer == NULL) return;
265     sprintf(filename_buffer, "%s%s%s", info_dir, base_name, trashinfo_suffix);
266     unlink(filename_buffer);
267     SHFree(filename_buffer);
268 }
269
270 static BOOL TRASH_MoveFileToBucket(TRASH_BUCKET *pBucket, const char *unix_path)
271 {
272     struct stat file_stat;
273     char *trash_file_name = NULL;
274     char *trash_path = NULL;
275     BOOL ret = TRUE;
276
277     if (lstat(unix_path, &file_stat)==-1)
278         return FALSE;
279     if (!file_good_for_bucket(pBucket, &file_stat))
280         return FALSE;
281         
282     trash_file_name = create_trashinfo(pBucket->info_dir, unix_path);
283     if (trash_file_name == NULL)
284         return FALSE;
285         
286     trash_path = SHAlloc(strlen(pBucket->files_dir)+strlen(trash_file_name)+1);
287     if (trash_path == NULL) goto error;
288     lstrcpyA(trash_path, pBucket->files_dir);
289     lstrcatA(trash_path, trash_file_name);
290     
291     if (rename(unix_path, trash_path)==0)
292     {
293         TRACE("rename succeded\n");
294         goto cleanup;
295     }
296     
297     /* TODO: try to manually move the file */
298     ERR("Couldn't move file\n");
299 error:
300     ret = FALSE;
301     remove_trashinfo_file(pBucket->info_dir, trash_file_name);
302 cleanup:
303     SHFree(trash_file_name);
304     SHFree(trash_path);
305     return ret;
306 }
307
308 BOOL TRASH_TrashFile(LPCWSTR wszPath)
309 {
310     char *unix_path;
311     BOOL result;
312     
313     TRACE("(%s)\n", debugstr_w(wszPath));
314     if (!TRASH_EnsureInitialized()) return FALSE;
315     if (!(unix_path = wine_get_unix_file_name(wszPath)))
316         return FALSE;
317     result = TRASH_MoveFileToBucket(home_trash, unix_path);
318     HeapFree(GetProcessHeap(), 0, unix_path);
319     return result;
320 }
321
322 /*
323  * The item ID of a trashed element is built as follows:
324  *  NUL byte                    - in most PIDLs the first byte is the type so we keep it constant
325  *  WIN32_FIND_DATAW structure  - with data about original file attributes
326  *  bucket name                 - currently only an empty string meaning the home bucket is supported
327  *  trash file name             - a NUL-terminated string
328  */
329 struct tagTRASH_ELEMENT
330 {
331     TRASH_BUCKET *bucket;
332     LPSTR filename;
333 };
334
335 static HRESULT TRASH_CreateSimplePIDL(const TRASH_ELEMENT *element, const WIN32_FIND_DATAW *data, LPITEMIDLIST *pidlOut)
336 {
337     LPITEMIDLIST pidl = SHAlloc(2+1+sizeof(WIN32_FIND_DATAW)+1+lstrlenA(element->filename)+1+2);
338     *pidlOut = NULL;
339     if (pidl == NULL)
340         return E_OUTOFMEMORY;
341     pidl->mkid.cb = (USHORT)(2+1+sizeof(WIN32_FIND_DATAW)+1+lstrlenA(element->filename)+1);
342     pidl->mkid.abID[0] = 0;
343     memcpy(pidl->mkid.abID+1, data, sizeof(WIN32_FIND_DATAW));
344     pidl->mkid.abID[1+sizeof(WIN32_FIND_DATAW)] = 0;
345     lstrcpyA((LPSTR)(pidl->mkid.abID+1+sizeof(WIN32_FIND_DATAW)+1), element->filename);
346     *(USHORT *)(pidl->mkid.abID+1+sizeof(WIN32_FIND_DATAW)+1+lstrlenA(element->filename)+1) = 0;
347     *pidlOut = pidl;
348     return S_OK;
349 }
350
351 /***********************************************************************
352  *      TRASH_UnpackItemID [Internal]
353  *
354  * DESCRIPTION:
355  * Extract the information stored in an Item ID. The TRASH_ELEMENT
356  * identifies the element in the Trash. The WIN32_FIND_DATA contains the
357  * information about the original file. The data->ftLastAccessTime contains
358  * the deletion time
359  *
360  * PARAMETER(S):
361  * [I] id : the ID of the item
362  * [O] element : the trash element this item id contains. Can be NULL if not needed
363  * [O] data : the WIN32_FIND_DATA of the original file. Can be NULL is not needed
364  */                 
365 HRESULT TRASH_UnpackItemID(LPCSHITEMID id, TRASH_ELEMENT *element, WIN32_FIND_DATAW *data)
366 {
367     if (id->cb < 2+1+sizeof(WIN32_FIND_DATAW)+2)
368         return E_INVALIDARG;
369     if (id->abID[0] != 0 || id->abID[1+sizeof(WIN32_FIND_DATAW)] != 0)
370         return E_INVALIDARG;
371     if (memchr(id->abID+1+sizeof(WIN32_FIND_DATAW)+1, 0, id->cb-(2+1+sizeof(WIN32_FIND_DATAW)+1)) == NULL)
372         return E_INVALIDARG;
373
374     if (data != NULL)
375         *data = *(WIN32_FIND_DATAW *)(id->abID+1);
376     if (element != NULL)
377     {
378         element->bucket = home_trash;
379         element->filename = StrDupA((LPCSTR)(id->abID+1+sizeof(WIN32_FIND_DATAW)+1));
380         if (element->filename == NULL)
381             return E_OUTOFMEMORY;
382     }
383     return S_OK;
384 }
385
386 void TRASH_DisposeElement(TRASH_ELEMENT *element)
387 {
388     SHFree(element->filename);
389 }
390
391 static HRESULT TRASH_GetDetails(const TRASH_ELEMENT *element, WIN32_FIND_DATAW *data)
392 {
393     LPSTR path = NULL;
394     XDG_PARSED_FILE *parsed = NULL;
395     char *original_file_name = NULL;
396     char *deletion_date = NULL;
397     int fd = -1;
398     struct stat stats;
399     HRESULT ret = S_FALSE;
400     LPWSTR original_dos_name;
401     int suffix_length = lstrlenA(trashinfo_suffix);
402     int filename_length = lstrlenA(element->filename);
403     int files_length = lstrlenA(element->bucket->files_dir);
404     int path_length = max(lstrlenA(element->bucket->info_dir), files_length);
405     
406     path = SHAlloc(path_length + filename_length + 1);
407     if (path == NULL) return E_OUTOFMEMORY;
408     wsprintfA(path, "%s%s", element->bucket->files_dir, element->filename);
409     path[path_length + filename_length - suffix_length] = 0;  /* remove the '.trashinfo' */    
410     if (lstat(path, &stats) == -1)
411     {
412         ERR("Error accessing data file for trashinfo %s (errno=%d)\n", element->filename, errno);
413         goto failed;
414     }
415     
416     wsprintfA(path, "%s%s", element->bucket->info_dir, element->filename);
417     fd = open(path, O_RDONLY);
418     if (fd == -1)
419     {
420         ERR("Couldn't open trashinfo file %s (errno=%d)\n", path, errno);
421         goto failed;
422     }
423     
424     parsed = XDG_ParseDesktopFile(fd);
425     if (parsed == NULL)
426     {
427         ERR("Parse error in trashinfo file %s\n", path);
428         goto failed;
429     }
430     
431     original_file_name = XDG_GetStringValue(parsed, trashinfo_group, "Path", XDG_URLENCODE);
432     if (original_file_name == NULL)
433     {
434         ERR("No 'Path' entry in trashinfo file\n");
435         goto failed;
436     }
437     
438     ZeroMemory(data, sizeof(*data));
439     data->nFileSizeHigh = (DWORD)((LONGLONG)stats.st_size>>32);
440     data->nFileSizeLow = stats.st_size & 0xffffffff;
441     RtlSecondsSince1970ToTime(stats.st_mtime, (LARGE_INTEGER *)&data->ftLastWriteTime);
442     
443     original_dos_name = wine_get_dos_file_name(original_file_name);
444     if (original_dos_name != NULL)
445     {
446         lstrcpynW(data->cFileName, original_dos_name, MAX_PATH);
447         SHFree(original_dos_name);
448     }
449     else
450     {
451         /* show only the file name */
452         char *filename = strrchr(original_file_name, '/');
453         if (filename == NULL)
454             filename = original_file_name;
455         MultiByteToWideChar(CP_UNIXCP, 0, filename, -1, data->cFileName, MAX_PATH);
456     }
457     
458     deletion_date = XDG_GetStringValue(parsed, trashinfo_group, "DeletionDate", 0);
459     if (deletion_date)
460     {
461         struct tm del_time;
462         time_t del_secs;
463         
464         sscanf(deletion_date, "%d-%d-%dT%d:%d:%d",
465             &del_time.tm_year, &del_time.tm_mon, &del_time.tm_mday,
466             &del_time.tm_hour, &del_time.tm_min, &del_time.tm_sec);
467         del_time.tm_year -= 1900;
468         del_time.tm_mon--;
469         del_secs = mktime(&del_time);
470         
471         RtlSecondsSince1970ToTime(del_secs, (LARGE_INTEGER *)&data->ftLastAccessTime);
472     }
473     
474     ret = S_OK;
475 failed:
476     SHFree(path);
477     SHFree(original_file_name);
478     SHFree(deletion_date);
479     if (fd != -1)
480         close(fd);
481     XDG_FreeParsedFile(parsed);
482     return ret;
483 }
484
485 static INT CALLBACK free_item_callback(void *item, void *lParam)
486 {
487     SHFree(item);
488     return TRUE;
489 }
490
491 static HDPA enum_bucket_trashinfos(const TRASH_BUCKET *bucket, int *count)
492 {
493     HDPA ret = DPA_Create(32);
494     struct dirent *entry;
495     DIR *dir = NULL;
496     
497     errno = ENOMEM;
498     *count = 0;
499     if (ret == NULL) goto failed;
500     dir = opendir(bucket->info_dir);
501     if (dir == NULL) goto failed;
502     while ((entry = readdir(dir)) != NULL)
503     {
504         LPSTR filename;
505         int namelen = lstrlenA(entry->d_name);
506         int suffixlen = lstrlenA(trashinfo_suffix);
507         if (namelen <= suffixlen ||
508                 lstrcmpA(entry->d_name+namelen-suffixlen, trashinfo_suffix) != 0)
509             continue;
510
511         filename = StrDupA(entry->d_name);
512         if (filename == NULL)
513             goto failed;
514         if (DPA_InsertPtr(ret, DPA_APPEND, filename) == -1)
515         {
516             SHFree(filename);
517             goto failed;
518         }
519         (*count)++;
520     }
521     closedir(dir);
522     return ret;
523 failed:
524     if (dir) closedir(dir);
525     if (ret)
526         DPA_DestroyCallback(ret, free_item_callback, NULL);
527     return NULL;
528 }
529
530 HRESULT TRASH_EnumItems(LPITEMIDLIST **pidls, int *count)
531 {
532     int ti_count;
533     int pos=0, i;
534     HRESULT err = E_OUTOFMEMORY;
535     HDPA tinfs;
536     
537     if (!TRASH_EnsureInitialized()) return E_FAIL;
538     tinfs = enum_bucket_trashinfos(home_trash, &ti_count);
539     if (tinfs == NULL) return E_FAIL;
540     *pidls = SHAlloc(sizeof(LPITEMIDLIST)*ti_count);
541     if (!*pidls) goto failed;
542     for (i=0; i<ti_count; i++)
543     {
544         WIN32_FIND_DATAW data;
545         TRASH_ELEMENT elem;
546         
547         elem.bucket = home_trash;
548         elem.filename = DPA_GetPtr(tinfs, i);
549         if (FAILED(err = TRASH_GetDetails(&elem, &data)))
550             goto failed;
551         if (err == S_FALSE)
552             continue;
553         if (FAILED(err = TRASH_CreateSimplePIDL(&elem, &data, &(*pidls)[pos])))
554             goto failed;
555         pos++;
556     }
557     *count = pos;
558     DPA_DestroyCallback(tinfs, free_item_callback, NULL);
559     return S_OK;
560 failed:
561     if (*pidls != NULL)
562     {
563         int j;
564         for (j=0; j<pos; j++)
565             SHFree((*pidls)[j]);
566         SHFree(*pidls);
567     }
568     DPA_DestroyCallback(tinfs, free_item_callback, NULL);
569     
570     return err;
571 }