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