132
|
1 /******************************************************************************
|
|
2 *
|
|
3 * copyright: Copyright © 2007 Daniel Keep. All rights reserved.
|
|
4 * license: BSD style: $(LICENSE)
|
|
5 * version: Initial release: December 2007
|
|
6 * authors: Daniel Keep
|
|
7 * credits: Thanks to John Reimer for helping test this module under
|
|
8 * Linux.
|
|
9 *
|
|
10 ******************************************************************************/
|
|
11
|
|
12 module tango.io.TempFile;
|
|
13
|
|
14 import tango.math.Random : Random;
|
|
15 import tango.io.DeviceConduit : DeviceConduit;
|
|
16 import tango.io.FileConduit : FileConduit;
|
|
17 import tango.io.FilePath : FilePath/*, PathView*/;
|
|
18 import tango.stdc.stringz : toStringz, toString16z;
|
|
19
|
|
20 /******************************************************************************
|
|
21 ******************************************************************************/
|
|
22
|
|
23 version( Win32 )
|
|
24 {
|
|
25 import tango.sys.Common : DWORD, LONG;
|
|
26
|
|
27 enum : DWORD { FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 }
|
|
28
|
|
29 version( Win32SansUnicode )
|
|
30 {
|
|
31 import tango.sys.Common :
|
|
32 GetVersionExA, OSVERSIONINFO,
|
|
33 CreateFileA, GENERIC_READ, GENERIC_WRITE,
|
|
34 CREATE_NEW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_DELETE_ON_CLOSE,
|
|
35 FILE_SHARE_READ, FILE_SHARE_WRITE,
|
|
36 LPSECURITY_ATTRIBUTES,
|
|
37 HANDLE, INVALID_HANDLE_VALUE,
|
|
38 GetTempPathA, SetFilePointer, GetLastError, ERROR_SUCCESS;
|
|
39
|
|
40 HANDLE CreateFile(FilePath fn, DWORD da, DWORD sm,
|
|
41 LPSECURITY_ATTRIBUTES sa, DWORD cd, DWORD faa, HANDLE tf)
|
|
42 {
|
|
43 return CreateFileA(fn.cString.ptr, da, sm, sa, cd, faa, tf);
|
|
44 }
|
|
45
|
|
46 char[] GetTempPath()
|
|
47 {
|
|
48 auto len = GetTempPathA(0, null);
|
|
49 if( len == 0 )
|
|
50 throw new Exception("could not obtain temporary path");
|
|
51
|
|
52 auto result = new char[len+1];
|
|
53 len = GetTempPathA(len+1, result.ptr);
|
|
54 if( len == 0 )
|
|
55 throw new Exception("could not obtain temporary path");
|
|
56 return result[0..len];
|
|
57 }
|
|
58 }
|
|
59 else
|
|
60 {
|
|
61 import tango.sys.Common :
|
|
62 GetVersionExW, OSVERSIONINFO,
|
|
63 CreateFileW, GENERIC_READ, GENERIC_WRITE,
|
|
64 CREATE_NEW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_DELETE_ON_CLOSE,
|
|
65 FILE_SHARE_READ, FILE_SHARE_WRITE,
|
|
66 LPSECURITY_ATTRIBUTES,
|
|
67 HANDLE, INVALID_HANDLE_VALUE,
|
|
68 GetTempPathW, SetFilePointer, GetLastError, ERROR_SUCCESS;
|
|
69
|
|
70 import tango.text.convert.Utf : toString, toString16;
|
|
71
|
|
72 HANDLE CreateFile(FilePath fn, DWORD da, DWORD sm,
|
|
73 LPSECURITY_ATTRIBUTES sa, DWORD cd, DWORD faa, HANDLE tf)
|
|
74 {
|
|
75 return CreateFileW(toString16(fn.cString).ptr,
|
|
76 da, sm, sa, cd, faa, tf);
|
|
77 }
|
|
78
|
|
79 char[] GetTempPath()
|
|
80 {
|
|
81 auto len = GetTempPathW(0, null);
|
|
82 if( len == 0 )
|
|
83 throw new Exception("could not obtain temporary path");
|
|
84
|
|
85 auto result = new wchar[len+1];
|
|
86 len = GetTempPathW(len+1, result.ptr);
|
|
87 if( len == 0 )
|
|
88 throw new Exception("could not obtain temporary path");
|
|
89 return toString(result[0..len]);
|
|
90 }
|
|
91 }
|
|
92
|
|
93 // Determines if reparse points (aka: symlinks) are supported. Support
|
|
94 // was introduced in Windows Vista.
|
|
95 bool reparseSupported()
|
|
96 {
|
|
97 OSVERSIONINFO versionInfo;
|
|
98 versionInfo.dwOSVersionInfoSize = versionInfo.sizeof;
|
|
99
|
|
100 void e(){throw new Exception("could not determine Windows version");};
|
|
101
|
|
102 version( Win32SansUnicode )
|
|
103 if( !GetVersionExA(&versionInfo) ) e();
|
|
104 else
|
|
105 if( !GetVersionExW(&versionInfo) ) e();
|
|
106
|
|
107 return (versionInfo.dwMajorVersion >= 6);
|
|
108 }
|
|
109 }
|
|
110
|
|
111 else version( Posix )
|
|
112 {
|
|
113 import tango.stdc.posix.fcntl : open, O_CREAT, O_EXCL, O_RDWR;
|
|
114 import tango.stdc.posix.pwd : getpwnam;
|
|
115 import tango.stdc.posix.unistd : access, getuid, lseek, unlink, W_OK;
|
|
116 import tango.stdc.posix.sys.stat : stat, stat_t;
|
|
117
|
|
118 import tango.sys.Environment : Environment;
|
|
119
|
|
120 enum { O_LARGEFILE = 0x8000 }
|
|
121
|
|
122 version( linux )
|
|
123 {
|
|
124 // NOTE: This constant is actually platform-dependant for some
|
|
125 // God-only-knows reason. *sigh* It should be fine for 'generic'
|
|
126 // architectures, but other ones will need to be double-checked.
|
|
127 enum { O_NOFOLLOW = 00400000 }
|
|
128 }
|
|
129 else version( darwin )
|
|
130 {
|
|
131 enum { O_NOFOLLOW = 0x0100 }
|
|
132 }
|
|
133 else
|
|
134 {
|
|
135 pragma(msg, "Cannot use TempFile: O_NOFOLLOW is not "
|
|
136 "defined for this platform.");
|
|
137 static assert(false);
|
|
138 }
|
|
139 }
|
|
140
|
|
141 /******************************************************************************
|
|
142 *
|
|
143 * The TempFile class aims to provide a safe way of creating and destroying
|
|
144 * temporary files. The TempFile class will automatically close temporary
|
|
145 * files when the object is destroyed, so it is recommended that you make
|
|
146 * appropriate use of scoped destruction.
|
|
147 *
|
|
148 * Temporary files can be created with one of several styles, much like normal
|
|
149 * FileConduits. TempFile styles have the following properties:
|
|
150 *
|
|
151 * $(UL
|
|
152 * $(LI $(B Transience): this determines whether the file should be destroyed
|
|
153 * as soon as it is closed (transient,) or continue to persist even after the
|
|
154 * application has terminated (permanent.))
|
|
155 * )
|
|
156 *
|
|
157 * Eventually, this will be expanded to give you greater control over the
|
|
158 * temporary file's properties.
|
|
159 *
|
|
160 * For the typical use-case (creating a file to temporarily store data too
|
|
161 * large to fit into memory,) the following is sufficient:
|
|
162 *
|
|
163 * -----
|
|
164 * {
|
|
165 * scope temp = new TempFile;
|
|
166 *
|
|
167 * // Use temp as a normal conduit; it will be automatically closed when
|
|
168 * // it goes out of scope.
|
|
169 * }
|
|
170 * -----
|
|
171 *
|
|
172 * $(B Important):
|
|
173 * It is recommended that you $(I do not) use files created by this class to
|
|
174 * store sensitive information. There are several known issues with the
|
|
175 * current implementation that could allow an attacker to access the contents
|
|
176 * of these temporary files.
|
|
177 *
|
|
178 * $(B Todo): Detail security properties and guarantees.
|
|
179 *
|
|
180 ******************************************************************************/
|
|
181
|
|
182 class TempFile : DeviceConduit, DeviceConduit.Seek
|
|
183 {
|
|
184 //alias FileConduit.Cache Cache;
|
|
185 //alias FileConduit.Share Share;
|
|
186
|
|
187 /+enum Visibility : ubyte
|
|
188 {
|
|
189 /**
|
|
190 * The temporary file will have read and write access to it restricted
|
|
191 * to the current user.
|
|
192 */
|
|
193 User,
|
|
194 /**
|
|
195 * The temporary file will have read and write access available to any
|
|
196 * user on the system.
|
|
197 */
|
|
198 World
|
|
199 }+/
|
|
200
|
|
201 /**************************************************************************
|
|
202 *
|
|
203 * This enumeration is used to control whether the temporary file should
|
|
204 * persist after the TempFile object has been destroyed.
|
|
205 *
|
|
206 **************************************************************************/
|
|
207
|
|
208 enum Transience : ubyte
|
|
209 {
|
|
210 /**
|
|
211 * The temporary file should be destroyed along with the owner object.
|
|
212 */
|
|
213 Transient,
|
|
214 /**
|
|
215 * The temporary file should persist after the object has been
|
|
216 * destroyed.
|
|
217 */
|
|
218 Permanent
|
|
219 }
|
|
220
|
|
221 /+enum Sensitivity : ubyte
|
|
222 {
|
|
223 /**
|
|
224 * Transient files will be truncated to zero length immediately
|
|
225 * before closure to prevent casual filesystem inspection to recover
|
|
226 * their contents.
|
|
227 *
|
|
228 * No additional action is taken on permanent files.
|
|
229 */
|
|
230 None,
|
|
231 /**
|
|
232 * Transient files will be zeroed-out before truncation, to mask their
|
|
233 * contents from more thorough filesystem inspection.
|
|
234 *
|
|
235 * This option is not compatible with permanent files.
|
|
236 */
|
|
237 Low
|
|
238 /+
|
|
239 /**
|
|
240 * Transient files will be overwritten first with zeroes, then with
|
|
241 * ones, and then with a random 32- or 64-bit pattern (dependant on
|
|
242 * which is most efficient.) The file will then be truncated.
|
|
243 *
|
|
244 * This option is not compatible with permanent files.
|
|
245 */
|
|
246 Medium
|
|
247 +/
|
|
248 }+/
|
|
249
|
|
250 /**************************************************************************
|
|
251 *
|
|
252 * This structure is used to determine how the temporary files should be
|
|
253 * opened and used.
|
|
254 *
|
|
255 **************************************************************************/
|
|
256 struct Style
|
|
257 {
|
|
258 align(1):
|
|
259 //Visibility visibility; ///
|
|
260 Transience transience; ///
|
|
261 //Sensitivity sensitivity; ///
|
|
262 //Share share; ///
|
|
263 //Cache cache; ///
|
|
264 int attempts = 10; ///
|
|
265 }
|
|
266
|
|
267 /**
|
|
268 * Style for creating a transient temporary file that only the current
|
|
269 * user can access.
|
|
270 */
|
|
271 static const Style Transient = {Transience.Transient};
|
|
272 /**
|
|
273 * Style for creating a permanent temporary file that only the current
|
|
274 * user can access.
|
|
275 */
|
|
276 static const Style Permanent = {Transience.Permanent};
|
|
277
|
|
278 // Path to the temporary file
|
|
279 private /*PathView*/ FilePath _path;
|
|
280
|
|
281 // Style we've opened with
|
|
282 private Style _style;
|
|
283
|
|
284 // Have we been detatched?
|
|
285 private bool detached = false;
|
|
286
|
|
287 ///
|
|
288 this(Style style = Style.init)
|
|
289 {
|
|
290 create(style);
|
|
291 }
|
|
292
|
|
293 ///
|
|
294 this(char[] prefix, Style style = Style.init)
|
|
295 {
|
|
296 this(FilePath(prefix), style);
|
|
297 }
|
|
298
|
|
299 ///
|
|
300 this(FilePath prefix, Style style = Style.init)
|
|
301 {
|
|
302 create(prefix.dup, style);
|
|
303 }
|
|
304
|
|
305 ~this()
|
|
306 {
|
|
307 if( !detached ) this.detach();
|
|
308 }
|
|
309
|
|
310 /**************************************************************************
|
|
311 *
|
|
312 * Returns a PathView to the temporary file. Please note that depending
|
|
313 * on your platform, the returned path may or may not actually exist if
|
|
314 * you specified a transient file.
|
|
315 *
|
|
316 **************************************************************************/
|
|
317 /*PathView*/ FilePath path()
|
|
318 {
|
|
319 return _path;
|
|
320 }
|
|
321
|
|
322 /**************************************************************************
|
|
323 *
|
|
324 * Indicates the style that this TempFile was created with.
|
|
325 *
|
|
326 **************************************************************************/
|
|
327 Style style()
|
|
328 {
|
|
329 return _style;
|
|
330 }
|
|
331
|
|
332 override char[] toString()
|
|
333 {
|
|
334 if( path.toString.length > 0 )
|
|
335 return path.toString;
|
|
336 else
|
|
337 return "<TempFile>";
|
|
338 }
|
|
339
|
|
340 /**************************************************************************
|
|
341 *
|
|
342 * Returns the current cursor position within the file.
|
|
343 *
|
|
344 **************************************************************************/
|
|
345 long position()
|
|
346 {
|
|
347 return seek(0, Seek.Anchor.Current);
|
|
348 }
|
|
349
|
|
350 /**************************************************************************
|
|
351 *
|
|
352 * Returns the total length, in bytes, of the temporary file.
|
|
353 *
|
|
354 **************************************************************************/
|
|
355 long length()
|
|
356 {
|
|
357 long pos, ret;
|
|
358 pos = position;
|
|
359 ret = seek(0, Seek.Anchor.End);
|
|
360 seek(pos);
|
|
361 return ret;
|
|
362 }
|
|
363
|
|
364 /*
|
|
365 * Creates a new temporary file with the given style.
|
|
366 */
|
|
367 private void create(Style style)
|
|
368 {
|
|
369 create(FilePath(tempPath).dup, style);
|
|
370 }
|
|
371
|
|
372 private void create(FilePath prefix, Style style)
|
|
373 {
|
|
374 for( size_t i=0; i<style.attempts; ++i )
|
|
375 {
|
|
376 if( create_path(prefix.dup.append(randomName), style) )
|
|
377 return;
|
|
378 }
|
|
379
|
|
380 error("could not create temporary file");
|
|
381 }
|
|
382
|
|
383 version( Win32 )
|
|
384 {
|
|
385 private static const DEFAULT_LENGTH = 6;
|
|
386 private static const DEFAULT_PREFIX = "~t";
|
|
387 private static const DEFAULT_SUFFIX = ".tmp";
|
|
388
|
|
389 private static const JUNK_CHARS =
|
|
390 "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
391
|
|
392 /*
|
|
393 * Returns the path to the temporary directory.
|
|
394 */
|
|
395 private char[] tempPath()
|
|
396 {
|
|
397 return GetTempPath();
|
|
398 }
|
|
399
|
|
400 /*
|
|
401 * Creates a new temporary file at the given path, with the specified
|
|
402 * style.
|
|
403 */
|
|
404 private bool create_path(FilePath path, Style style)
|
|
405 {
|
|
406 // TODO: Check permissions directly and throw an exception;
|
|
407 // otherwise, we could spin trying to make a file when it's
|
|
408 // actually not possible.
|
|
409
|
|
410 // This code is largely stolen from FileConduit
|
|
411 DWORD attr, share, access, create;
|
|
412
|
|
413 alias DWORD[] Flags;
|
|
414
|
|
415 // Basic stuff
|
|
416 access = GENERIC_READ | GENERIC_WRITE;
|
|
417 share = 0; // No sharing
|
|
418 create = CREATE_NEW;
|
|
419
|
|
420 // Set up flags
|
|
421 attr = FILE_ATTRIBUTE_NORMAL;
|
|
422 attr |= reparseSupported ? FILE_FLAG_OPEN_REPARSE_POINT : 0;
|
|
423 if( style.transience == Transience.Transient )
|
|
424 attr |= FILE_FLAG_DELETE_ON_CLOSE;
|
|
425
|
|
426 handle = CreateFile(
|
|
427 /*lpFileName*/ path,
|
|
428 /*dwDesiredAccess*/ access,
|
|
429 /*dwShareMode*/ share,
|
|
430 /*lpSecurityAttributes*/ null,
|
|
431 /*dwCreationDisposition*/ CREATE_NEW,
|
|
432 /*dwFlagsAndAttributes*/ attr,
|
|
433 /*hTemplateFile*/ null
|
|
434 );
|
|
435
|
|
436 if( handle is INVALID_HANDLE_VALUE )
|
|
437 return false;
|
|
438
|
|
439 _path = path;
|
|
440 _style = style;
|
|
441 return true;
|
|
442 }
|
|
443
|
|
444 // See DDoc version
|
|
445 long seek(long offset, Seek.Anchor anchor = Seek.Anchor.Begin)
|
|
446 {
|
|
447 LONG high = cast(LONG) (offset >> 32);
|
|
448 long result = SetFilePointer (handle, cast(LONG) offset,
|
|
449 &high, anchor);
|
|
450
|
|
451 if (result is -1 &&
|
|
452 GetLastError() != ERROR_SUCCESS)
|
|
453 error();
|
|
454
|
|
455 return result + (cast(long) high << 32);
|
|
456 }
|
|
457 }
|
|
458 else version( Posix )
|
|
459 {
|
|
460 private static const DEFAULT_LENGTH = 6;
|
|
461 private static const DEFAULT_PREFIX = ".tmp";
|
|
462
|
|
463 // Use "~" to work around a bug in DMD where it elides empty constants
|
|
464 private static const DEFAULT_SUFFIX = "~";
|
|
465
|
|
466 private static const JUNK_CHARS =
|
|
467 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
468 "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
469
|
|
470 /*
|
|
471 * Returns the path to the temporary directory.
|
|
472 */
|
|
473 private char[] tempPath()
|
|
474 {
|
|
475 // Check for TMPDIR; failing that, use /tmp
|
|
476 if( auto tmpdir = Environment.get("TMPDIR") )
|
|
477 return tmpdir;
|
|
478 else
|
|
479 return "/tmp/";
|
|
480 }
|
|
481
|
|
482 /*
|
|
483 * Creates a new temporary file at the given path, with the specified
|
|
484 * style.
|
|
485 */
|
|
486 private bool create_path(FilePath path, Style style)
|
|
487 {
|
|
488 // Check suitability
|
|
489 {
|
|
490 auto parent = path.path;
|
|
491 auto parentz = toStringz(parent);
|
|
492
|
|
493 // Make sure we have write access
|
|
494 if( access(parentz, W_OK) == -1 )
|
|
495 error("do not have write access to temporary directory");
|
|
496
|
|
497 // Get info on directory
|
|
498 stat_t sb;
|
|
499 if( stat(parentz, &sb) == -1 )
|
|
500 error("could not stat temporary directory");
|
|
501
|
|
502 // Get root's UID
|
|
503 auto pwe = getpwnam("root");
|
|
504 if( pwe is null ) error("could not get root's uid");
|
|
505 auto root_uid = pwe.pw_uid;
|
|
506
|
|
507 // Make sure either we or root are the owner
|
|
508 if( !(sb.st_uid == root_uid || sb.st_uid == getuid) )
|
|
509 error("temporary directory owned by neither root nor user");
|
|
510
|
|
511 // Check to see if anyone other than us can write to the dir.
|
|
512 if( (sb.st_mode & 022) != 0 && (sb.st_mode & 01000) == 0 )
|
|
513 error("sticky bit not set on world-writable directory");
|
|
514 }
|
|
515
|
|
516 // Create file
|
|
517 {
|
|
518 auto flags = O_LARGEFILE | O_CREAT | O_EXCL
|
|
519 | O_NOFOLLOW | O_RDWR;
|
|
520
|
|
521 auto pathz = path.cString.ptr;
|
|
522
|
|
523 handle = open(pathz, flags, 0600);
|
|
524 if( handle is -1 )
|
|
525 return false;
|
|
526
|
|
527 if( style.transience == Transience.Transient )
|
|
528 {
|
|
529 // BUG TODO: check to make sure the path still points
|
|
530 // to the file we opened. Pity you can't unlink a file
|
|
531 // descriptor...
|
|
532
|
|
533 // NOTE: This should be an exception and not simply
|
|
534 // returning false, since this is a violation of our
|
|
535 // guarantees.
|
|
536 if( unlink(pathz) == -1 )
|
|
537 error("could not remove transient file");
|
|
538 }
|
|
539
|
|
540 _path = path;
|
|
541 _style = style;
|
|
542
|
|
543 return true;
|
|
544 }
|
|
545 }
|
|
546
|
|
547 // See DDoc version
|
|
548 long seek(long offset, Seek.Anchor anchor = Seek.Anchor.Begin)
|
|
549 {
|
|
550 long result = lseek(handle, offset, anchor);
|
|
551 if (result is -1)
|
|
552 error();
|
|
553 return result;
|
|
554 }
|
|
555 }
|
|
556 else version( DDoc )
|
|
557 {
|
|
558 /**********************************************************************
|
|
559 *
|
|
560 * Seeks the temporary file's cursor to the given location.
|
|
561 *
|
|
562 **********************************************************************/
|
|
563 long seek(long offset, Seek.Anchor anchor = Seek.Anchor.Begin);
|
|
564 }
|
|
565 else
|
|
566 {
|
|
567 static assert(false, "Unsupported platform");
|
|
568 }
|
|
569
|
|
570 /*
|
|
571 * Generates a new random file name, sans directory.
|
|
572 */
|
|
573 private char[] randomName(uint length=DEFAULT_LENGTH,
|
|
574 char[] prefix=DEFAULT_PREFIX,
|
|
575 char[] suffix=DEFAULT_SUFFIX)
|
|
576 {
|
|
577 auto junk = new char[length];
|
|
578 scope(exit) delete junk;
|
|
579
|
|
580 foreach( ref c ; junk )
|
|
581 c = JUNK_CHARS[Random.shared.next($)];
|
|
582
|
|
583 return prefix~junk~suffix;
|
|
584 }
|
|
585
|
|
586 override void detach()
|
|
587 {
|
|
588 static assert( !is(Sensitivity) );
|
|
589 super.detach();
|
|
590 detached = true;
|
|
591 }
|
|
592 }
|
|
593
|
|
594 version( TempFile_SelfTest ):
|
|
595
|
|
596 import tango.io.Console : Cin;
|
|
597 import tango.io.Stdout : Stdout;
|
|
598
|
|
599 void main()
|
|
600 {
|
|
601 Stdout(r"
|
|
602 Please ensure that the transient file no longer exists once the TempFile
|
|
603 object is destroyed, and that the permanent file does. You should also check
|
|
604 the following on both:
|
|
605
|
|
606 * the file should be owned by you,
|
|
607 * the owner should have read and write permissions,
|
|
608 * no other permissions should be set on the file.
|
|
609
|
|
610 For POSIX systems:
|
|
611
|
|
612 * the temp directory should be owned by either root or you,
|
|
613 * if anyone other than root or you can write to it, the sticky bit should be
|
|
614 set,
|
|
615 * if the directory is writable by anyone other than root or the user, and the
|
|
616 sticky bit is *not* set, then creating the temporary file should fail.
|
|
617
|
|
618 You might want to delete the permanent one afterwards, too. :)")
|
|
619 .newline;
|
|
620
|
|
621 Stdout.formatln("Creating a transient file:");
|
|
622 {
|
|
623 scope tempFile = new TempFile(/*TempFile.UserPermanent*/);
|
|
624 scope(exit) tempFile.detach;
|
|
625
|
|
626 Stdout.formatln(" .. path: {}", tempFile);
|
|
627
|
|
628 tempFile.output.write("Transient temp file.");
|
|
629
|
|
630 char[] buffer = new char[1023];
|
|
631 tempFile.seek(0);
|
|
632 buffer = buffer[0..tempFile.input.read(buffer)];
|
|
633
|
|
634 Stdout.formatln(" .. contents: \"{}\"", buffer);
|
|
635
|
|
636 Stdout(" .. press Enter to destroy TempFile object.").newline;
|
|
637 Cin.copyln();
|
|
638 }
|
|
639
|
|
640 Stdout.newline;
|
|
641
|
|
642 Stdout.formatln("Creating a permanent file:");
|
|
643 {
|
|
644 scope tempFile = new TempFile(TempFile.UserPermanent);
|
|
645 scope(exit) tempFile.detach;
|
|
646
|
|
647 Stdout.formatln(" .. path: {}", tempFile);
|
|
648
|
|
649 tempFile.output.write("Permanent temp file.");
|
|
650
|
|
651 char[] buffer = new char[1023];
|
|
652 tempFile.seek(0);
|
|
653 buffer = buffer[0..tempFile.input.read(buffer)];
|
|
654
|
|
655 Stdout.formatln(" .. contents: \"{}\"", buffer);
|
|
656
|
|
657 Stdout(" .. press Enter to destroy TempFile object.").flush;
|
|
658 Cin.copyln();
|
|
659 }
|
|
660
|
|
661 Stdout("\nDone.").newline;
|
|
662 }
|
|
663
|