/src/graphicsmagick/magick/tempfile.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003-2019 GraphicsMagick Group |
3 | | % |
4 | | % This program is covered by multiple licenses, which are described in |
5 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
6 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
7 | | % |
8 | | % Temporary file allocation manager. |
9 | | % |
10 | | */ |
11 | | |
12 | | #include "magick/studio.h" |
13 | | #include "magick/error.h" |
14 | | #include "magick/log.h" |
15 | | #include "magick/random.h" |
16 | | #include "magick/semaphore.h" |
17 | | #include "magick/tempfile.h" |
18 | | #include "magick/utility.h" |
19 | | |
20 | | /* |
21 | | Define declarations. |
22 | | */ |
23 | | #if defined(S_IRUSR) && defined(S_IWUSR) |
24 | 672k | # define S_MODE (S_IRUSR | S_IWUSR) |
25 | | #elif defined (MSWINDOWS) |
26 | | # define S_MODE (_S_IREAD | _S_IWRITE) |
27 | | #else |
28 | | # define S_MODE 0600 |
29 | | #endif |
30 | | |
31 | | #if defined(MSWINDOWS) |
32 | | # if defined(_P_tmpdir) && !defined(P_tmpdir) |
33 | | # define P_tmpdir _P_tmpdir |
34 | | # endif |
35 | | #endif |
36 | | |
37 | | /* |
38 | | Type definitions |
39 | | */ |
40 | | typedef struct _TempfileInfo |
41 | | { |
42 | | char |
43 | | filename[MaxTextExtent]; |
44 | | |
45 | | struct _TempfileInfo |
46 | | *next; |
47 | | } TempfileInfo; |
48 | | |
49 | | static TempfileInfo |
50 | | *templist = 0; |
51 | | |
52 | | static SemaphoreInfo |
53 | | *templist_semaphore = 0; |
54 | | |
55 | | /* |
56 | | Add a temporary file to list |
57 | | */ |
58 | | static void AddTemporaryFileToList(const char *filename) |
59 | 672k | { |
60 | 672k | (void) LogMagickEvent(TemporaryFileEvent,GetMagickModule(), |
61 | 672k | "Allocating temporary file \"%s\"",filename); |
62 | 672k | LockSemaphoreInfo(templist_semaphore); |
63 | 672k | { |
64 | 672k | TempfileInfo |
65 | 672k | *info; |
66 | | |
67 | 672k | info=MagickAllocateMemory(TempfileInfo *,sizeof(TempfileInfo)); |
68 | 672k | if (info) |
69 | 672k | { |
70 | 672k | info->next=0; |
71 | 672k | (void) strlcpy(info->filename,filename,sizeof(info->filename)); |
72 | 672k | if (!templist) |
73 | 66.4k | templist=info; |
74 | 606k | else |
75 | 606k | { |
76 | 606k | info->next=templist; |
77 | 606k | templist=info; |
78 | 606k | } |
79 | 672k | } |
80 | 672k | } |
81 | 672k | UnlockSemaphoreInfo(templist_semaphore); |
82 | 672k | } |
83 | | |
84 | | /* |
85 | | Remove a temporary file from the list. |
86 | | Return MagickPass if removed or MagickFail if not found |
87 | | */ |
88 | | static MagickPassFail RemoveTemporaryFileFromList(const char *filename) |
89 | 347k | { |
90 | 347k | MagickPassFail |
91 | 347k | status=MagickFail; |
92 | | |
93 | 347k | (void) LogMagickEvent(TemporaryFileEvent,GetMagickModule(), |
94 | 347k | "Deallocating temporary file \"%s\"",filename); |
95 | | |
96 | 347k | LockSemaphoreInfo(templist_semaphore); |
97 | 347k | { |
98 | 347k | TempfileInfo |
99 | 347k | *current, |
100 | 347k | *previous=0; |
101 | | |
102 | 52.4M | for (current=templist; current; current=current->next) |
103 | 52.4M | { |
104 | 52.4M | if (strcmp(current->filename,filename) == 0) |
105 | 338k | { |
106 | 338k | if (previous) |
107 | 13.9k | previous->next=current->next; |
108 | 324k | else |
109 | 324k | templist=current->next; |
110 | 338k | MagickFreeMemory(current); |
111 | 338k | status=MagickPass; |
112 | 338k | break; |
113 | 338k | } |
114 | 52.1M | previous=current; |
115 | 52.1M | } |
116 | 347k | } |
117 | 347k | UnlockSemaphoreInfo(templist_semaphore); |
118 | 347k | return status; |
119 | 347k | } |
120 | | /* |
121 | | Compose a temporary file name based a template string provided by |
122 | | the user. Any 'X' characters are replaced with characters randomly |
123 | | selected from a "safe" set of characters which are unlikely to |
124 | | be interpreted by a shell or program and cause confusion. |
125 | | */ |
126 | | static void ComposeTemporaryFileName(char *name) |
127 | 672k | { |
128 | 672k | static const char SafeChars[] |
129 | 672k | = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
130 | | |
131 | 672k | char |
132 | 672k | *c; |
133 | | |
134 | 672k | assert(name != (char *) NULL); |
135 | | |
136 | 6.05M | for (c=name; *c; c++) |
137 | 5.38M | { |
138 | 5.38M | if (*c == 'X') |
139 | 4.03M | *c=SafeChars[MagickRandomInteger() % (sizeof(SafeChars)-1)]; |
140 | 5.38M | } |
141 | 672k | } |
142 | | |
143 | | /* |
144 | | Validate a temporary directory path |
145 | | */ |
146 | | static MagickPassFail ValidateTemporaryFileDirectory(const char *tempdir) |
147 | 672k | { |
148 | 672k | assert(tempdir != (char *) NULL); |
149 | | |
150 | 672k | if (tempdir[0] == '\0') |
151 | 0 | return MagickFail; |
152 | 672k | if (access(tempdir,W_OK) != 0) |
153 | 0 | return MagickFail; |
154 | 672k | return MagickPass; |
155 | 672k | } |
156 | | |
157 | | |
158 | | /* |
159 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
160 | | % % |
161 | | % % |
162 | | % % |
163 | | + A c q u i r e T e m p o r a r y F i l e N a m e % |
164 | | % % |
165 | | % % |
166 | | % % |
167 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
168 | | % |
169 | | % AcquireTemporaryFileName replaces the contents of the string buffer pointed |
170 | | % to by filename with a unique file name. MagickPass is returned if a file |
171 | | % name is allocated, or MagickFail is returned if there is a failure. |
172 | | % When the filename is allocated, a temporary file is created on disk with |
173 | | % zero length, and read/write permission by the user. |
174 | | % The allocated filename should be recovered via the LiberateTemporaryFile() |
175 | | % function once it is no longer required. |
176 | | % |
177 | | % The format of the AcquireTemporaryFileName method is: |
178 | | % |
179 | | % MagickPassFail AcquireTemporaryFileName(char *filename, |
180 | | % ExceptionInfo *exception) |
181 | | % |
182 | | % A description of each parameter follows. |
183 | | % |
184 | | % o status: MagickPass if a temporary file name is allocated and successfully |
185 | | % created on disk. MagickFail if the temporary file name or file |
186 | | % creation fails. |
187 | | % |
188 | | % o filename: Specifies a pointer to an array of characters. The unique |
189 | | % file name is returned in this array. |
190 | | % |
191 | | */ |
192 | | MagickExport MagickPassFail AcquireTemporaryFileName(char *filename) |
193 | 163k | { |
194 | 163k | int |
195 | 163k | fd; |
196 | | |
197 | 163k | assert(filename != (char *) NULL); |
198 | 163k | if ((fd=AcquireTemporaryFileDescriptor(filename)) != -1) |
199 | 163k | { |
200 | 163k | (void) close(fd); |
201 | 163k | return (MagickPass); |
202 | 163k | } |
203 | 0 | return (MagickFail); |
204 | 163k | } |
205 | | |
206 | | /* |
207 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
208 | | % % |
209 | | % % |
210 | | % % |
211 | | + A c q u i r e T e m p o r a r y F i l e D e s c r i p t o r % |
212 | | % % |
213 | | % % |
214 | | % % |
215 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
216 | | % |
217 | | % AcquireTemporaryFileDescriptor replaces the contents of the string buffer |
218 | | % pointed to by filename with a unique file name created on disk. A |
219 | | % read/write file descriptor (from open()) is returned on success, or -1 is |
220 | | % returned on failure. The temporary file is created on disk with read/write |
221 | | % permission by the user. The allocated temporary file should be deallocated |
222 | | % via the LiberateTemporaryFile() function once it is no longer required. |
223 | | % |
224 | | % The format of the AcquireTemporaryFileDescriptor method is: |
225 | | % |
226 | | % int AcquireTemporaryFileDescriptor(char *filename) |
227 | | % |
228 | | % A description of each parameter follows. |
229 | | % |
230 | | % o status: The file descriptor for an open file on success, or -1 |
231 | | % on failure. |
232 | | % |
233 | | % o filename: Specifies a pointer to an array of characters with an |
234 | | % allocated length of at least MaxTextExtent. The unique |
235 | | % file name associated with the descriptor is returned in |
236 | | % this array. |
237 | | % |
238 | | */ |
239 | | MagickExport int AcquireTemporaryFileDescriptor(char *filename) |
240 | 672k | { |
241 | 672k | static const char env_strings[][14] = |
242 | 672k | { |
243 | 672k | "MAGICK_TMPDIR" |
244 | 672k | #if defined(POSIX) |
245 | 672k | ,"TMPDIR" |
246 | 672k | #endif /* defined(POSIX) */ |
247 | | #if defined(MSWINDOWS) || defined(__CYGWIN__) |
248 | | ,"TMP" |
249 | | ,"TEMP" |
250 | | #endif |
251 | 672k | }; |
252 | | |
253 | | |
254 | 672k | char |
255 | 672k | tempdir[MaxTextExtent]; |
256 | | |
257 | 672k | unsigned int |
258 | 672k | i; |
259 | | |
260 | 672k | int |
261 | 672k | fd=-1; |
262 | | |
263 | 672k | assert(filename != (char *) NULL); |
264 | 672k | filename[0]='\0'; |
265 | 672k | tempdir[0]='\0'; |
266 | | |
267 | 2.01M | for (i=0; i < ArraySize(env_strings); i++) |
268 | 1.34M | { |
269 | 1.34M | const char |
270 | 1.34M | *env; |
271 | | |
272 | 1.34M | if ((env=getenv(env_strings[i])) != NULL) |
273 | 0 | { |
274 | 0 | const size_t copy_len = sizeof(tempdir)-16; |
275 | 0 | if (env_strings[i][0] == '\0') |
276 | 0 | break; |
277 | 0 | if (strlcpy(tempdir,env,copy_len) >= copy_len) |
278 | 0 | tempdir[0]='\0'; |
279 | 0 | if ((tempdir[0] != '\0') && |
280 | 0 | !ValidateTemporaryFileDirectory(tempdir)) |
281 | 0 | tempdir[0]='\0'; |
282 | 0 | if (tempdir[0] != '\0') |
283 | 0 | break; |
284 | 0 | } |
285 | 1.34M | } |
286 | | |
287 | 672k | #if defined(P_tmpdir) |
288 | 672k | if (tempdir[0] == '\0') |
289 | 672k | { |
290 | 672k | const size_t copy_len = sizeof(tempdir)-16; |
291 | 672k | if (strlcpy(tempdir,P_tmpdir,copy_len) >= copy_len) |
292 | 0 | tempdir[0]='\0'; |
293 | 672k | if ((tempdir[0] != '\0') && |
294 | 672k | !ValidateTemporaryFileDirectory(tempdir)) |
295 | 0 | tempdir[0]='\0'; |
296 | 672k | } |
297 | 672k | #endif |
298 | | |
299 | 672k | if (tempdir[0] != '\0') |
300 | 672k | { |
301 | 672k | char |
302 | 672k | tempname[16]; |
303 | | |
304 | 672k | int |
305 | 672k | tries=0; |
306 | | |
307 | 672k | for (tries=0; tries < 256; tries++) |
308 | 672k | { |
309 | 672k | (void) strlcpy(tempname,"gmXXXXXX",sizeof(tempname)); |
310 | 672k | ComposeTemporaryFileName(tempname); |
311 | 672k | if (strlcpy(filename,tempdir,MaxTextExtent) >= MaxTextExtent) |
312 | 0 | break; |
313 | 672k | if (filename[strlen(filename)-1] != DirectorySeparator[0]) |
314 | 672k | if (strlcat(filename,DirectorySeparator,MaxTextExtent) >= |
315 | 672k | MaxTextExtent) |
316 | 0 | break; |
317 | 672k | if (strlcat(filename,tempname,MaxTextExtent) >= MaxTextExtent) |
318 | 0 | break; |
319 | 672k | fd=open(filename,O_RDWR | O_CREAT | O_BINARY | O_EXCL, S_MODE); |
320 | 672k | if (fd != -1) |
321 | 672k | { |
322 | 672k | AddTemporaryFileToList(filename); |
323 | 672k | return (fd); |
324 | 672k | } |
325 | 672k | } |
326 | 672k | } |
327 | | |
328 | 0 | return fd; |
329 | 672k | } |
330 | | |
331 | | /* |
332 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
333 | | % % |
334 | | % % |
335 | | % % |
336 | | + A c q u i r e T e m p o r a r y F i l e S t r e a m % |
337 | | % % |
338 | | % % |
339 | | % % |
340 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
341 | | % |
342 | | % AcquireTemporaryFileStream replaces the contents of the string buffer |
343 | | % pointed to by filename with a unique file name. A pointer to an open |
344 | | % stdio FILE stream is returned on success, or a null pointer is returned |
345 | | % on failure. The temporary file is created on disk with read/write |
346 | | % permission by the user. The allocated temporary file should be deallocated |
347 | | % via the LiberateTemporaryFile() function once it is no longer required. |
348 | | % |
349 | | % The format of the AcquireTemporaryFile method is: |
350 | | % |
351 | | % FILE *AcquireTemporaryFileStream(char *filename,FileIOMode mode) |
352 | | % |
353 | | % A description of each parameter follows. |
354 | | % |
355 | | % o status: The file descriptor for an open file on success, or -1 |
356 | | % on failure. |
357 | | % |
358 | | % o filename: Specifies a pointer to an array of characters. The unique |
359 | | % file name is returned in this array. |
360 | | % |
361 | | % o mode: Set to TextFileIOMode to open the file in text mode, otherwise |
362 | | % the file is opened in binary mode. |
363 | | % |
364 | | */ |
365 | | MagickExport FILE *AcquireTemporaryFileStream(char *filename, |
366 | | FileIOMode mode) |
367 | 509k | { |
368 | 509k | int |
369 | 509k | fd; |
370 | | |
371 | 509k | if ((fd=AcquireTemporaryFileDescriptor(filename)) != -1) |
372 | 509k | { |
373 | 509k | if (mode == TextFileIOMode) |
374 | 117k | return fdopen(fd,"w+"); |
375 | 391k | return fdopen(fd,"wb+"); |
376 | 509k | } |
377 | | |
378 | 0 | return (FILE *) NULL; |
379 | 509k | } |
380 | | |
381 | | /* |
382 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
383 | | % % |
384 | | % % |
385 | | % % |
386 | | + D e s t r o y T e m p o r a r y F i l e s % |
387 | | % % |
388 | | % % |
389 | | % % |
390 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
391 | | % |
392 | | % DestroyTemporaryFiles reclaims all currently allocated temporary files, |
393 | | % removing the files from the filesystem if they still exist. Also destroys |
394 | | % the associated semaphore so that temporary file system can not be used |
395 | | % again without invoking InitializeTemporaryFiles() to re-initialize it. |
396 | | % |
397 | | % void DestroyTemporaryFiles(void) |
398 | | % |
399 | | */ |
400 | | MagickExport void DestroyTemporaryFiles(void) |
401 | 0 | { |
402 | 0 | PurgeTemporaryFiles(); |
403 | 0 | DestroySemaphoreInfo(&templist_semaphore); |
404 | 0 | } |
405 | | |
406 | | /* |
407 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
408 | | % % |
409 | | % % |
410 | | % % |
411 | | + I n i t i a l i z e T e m p o r a r y F i l e s % |
412 | | % % |
413 | | % % |
414 | | % % |
415 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
416 | | % |
417 | | % Method InitializeTemporaryFiles initializes the temporary file facility |
418 | | % |
419 | | % The format of the InitializeTemporaryFiles method is: |
420 | | % |
421 | | % MagickPassFail InitializeTemporaryFiles(void) |
422 | | % |
423 | | % |
424 | | */ |
425 | | MagickPassFail |
426 | | InitializeTemporaryFiles(void) |
427 | 252 | { |
428 | 252 | assert(templist_semaphore == (SemaphoreInfo *) NULL); |
429 | 252 | templist_semaphore=AllocateSemaphoreInfo(); |
430 | 252 | return MagickPass; |
431 | 252 | } |
432 | | |
433 | | /* |
434 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
435 | | % % |
436 | | % % |
437 | | % % |
438 | | + L i b e r a t e T e m p o r a r y F i l e % |
439 | | % % |
440 | | % % |
441 | | % % |
442 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
443 | | % |
444 | | % LiberateTemporaryFile deallocates the allocated temporary file, removing |
445 | | % the temporary file from the filesystem if it still exists. If the name |
446 | | % provided is a valid temporary file name, then the first position in the |
447 | | % string buffer is set to null in order to avoid accidental continued use. |
448 | | % If the name is not contained in the temporary file list, then it is left |
449 | | % unmodified. |
450 | | % |
451 | | % MagickPassFail LiberateTemporaryFile(char *filename) |
452 | | % |
453 | | % A description of each parameter follows. |
454 | | % |
455 | | % o filename: Specifies a pointer to an array of characters representing |
456 | | % the temporary file to reclaim. |
457 | | % |
458 | | */ |
459 | | MagickExport MagickPassFail LiberateTemporaryFile(char *filename) |
460 | 347k | { |
461 | 347k | MagickPassFail |
462 | 347k | status = MagickFail; |
463 | | |
464 | 347k | if (RemoveTemporaryFileFromList(filename)) |
465 | 338k | { |
466 | 338k | if ((remove(filename)) == 0) |
467 | 338k | status=MagickPass; |
468 | 1 | else |
469 | 1 | (void) LogMagickEvent(TemporaryFileEvent,GetMagickModule(), |
470 | 1 | "Temporary file removal failed \"%s\"",filename); |
471 | | /* Erase name so it is not accidentally used again */ |
472 | 338k | filename[0]='\0'; |
473 | 338k | } |
474 | 9.20k | else |
475 | 9.20k | (void) LogMagickEvent(TemporaryFileEvent,GetMagickModule(), |
476 | 9.20k | "Temporary file \"%s\" to be removed not allocated!",filename); |
477 | 347k | return (status); |
478 | 347k | } |
479 | | |
480 | | /* |
481 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
482 | | % % |
483 | | % % |
484 | | % % |
485 | | + P u r g e T e m p o r a r y F i l e s % |
486 | | % % |
487 | | % % |
488 | | % % |
489 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
490 | | % |
491 | | % PurgeTemporaryFiles reclaims all currently allocated temporary files, |
492 | | % removing the files from the filesystem if they still exist. The temporary |
493 | | % file allocation system remains usable after this function is called. |
494 | | % |
495 | | % void PurgeTemporaryFiles(void) |
496 | | % |
497 | | */ |
498 | | MagickExport void PurgeTemporaryFiles(void) |
499 | 0 | { |
500 | 0 | TempfileInfo |
501 | 0 | *member, |
502 | 0 | *liberate; |
503 | |
|
504 | 0 | member=templist; |
505 | 0 | templist=0; |
506 | 0 | while(member) |
507 | 0 | { |
508 | 0 | liberate=member; |
509 | 0 | member=member->next; |
510 | 0 | (void) LogMagickEvent(TemporaryFileEvent,GetMagickModule(), |
511 | 0 | "Removing leaked temporary file \"%s\"",liberate->filename); |
512 | 0 | if ((remove(liberate->filename)) != 0) |
513 | 0 | (void) LogMagickEvent(TemporaryFileEvent,GetMagickModule(), |
514 | 0 | "Temporary file removal failed \"%s\"",liberate->filename); |
515 | 0 | liberate->next=0; |
516 | 0 | MagickFreeMemory(liberate); |
517 | 0 | } |
518 | 0 | } |
519 | | |
520 | | /* |
521 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
522 | | % % |
523 | | % % |
524 | | % % |
525 | | + P u r g e T e m p o r a r y F i l e s A s y n c S a f e % |
526 | | % % |
527 | | % % |
528 | | % % |
529 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
530 | | % |
531 | | % PurgeTemporaryFiles reclaims all currently allocated temporary files, |
532 | | % removing the files from the filesystem if they still exist. This version |
533 | | % is similar to PurgeTemporaryFiles() except that it intentionally does |
534 | | % not release any memory (might use a mutex/semaphore) or invoke any |
535 | | % functions which are not async-safe. The only reason to call this function |
536 | | % is something has gone wrong and the program needs to quit immediately. |
537 | | % |
538 | | % void PurgeTemporaryFilesAsyncSafe(void) |
539 | | % |
540 | | */ |
541 | | MagickExport void PurgeTemporaryFilesAsyncSafe(void) |
542 | 0 | { |
543 | 0 | const TempfileInfo |
544 | 0 | *member; |
545 | |
|
546 | 0 | member=templist; |
547 | 0 | templist=0; |
548 | 0 | while(member) |
549 | 0 | { |
550 | 0 | (void) unlink(member->filename); |
551 | 0 | member=member->next; |
552 | 0 | } |
553 | 0 | } |