Difference between revisions of "Locating the macOS tmp directory"

From Lazarus wiki
m (Determining where to store files: Added TM note to tmp dir location)
(Storing temporary files: Code simplified at the suggestion of ChrisR)
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 +
{{Locating the macOS tmp directory}}
 
{{Platform only|macOS}}
 
{{Platform only|macOS}}
{{LanguageBar}}
 
  
 
== Overview ==
 
== Overview ==
  
To locate the current user's temporary directory in macOS you should use the native macOS Foundation function ''NSTemporaryDirectory()''. This directory is unique for each user, the user is guaranteed to have write permissions to it, and  it is in a hashed location which is not predictable in advance so that it is safe from security issues associated with predictable locations. It is also guaranteed to work when your application has been [[Sandboxing for OS X|sandboxed]].  
+
To locate the current user's temporary directory in macOS you should use the native macOS Foundation function ''NSTemporaryDirectory()''. This directory is unique for each user, the user is guaranteed to have write permissions to it, and  it is in a hashed location which is not predictable in advance so that it is safe from security issues associated with predictable locations. It is also guaranteed to work when your application has been [[Sandboxing for macOS|sandboxed]].  
  
You should be aware that this directory is cleaned out automatically every 3 days but otherwise persists between application launches and reboots.  
+
You should be aware that this directory is cleaned out automatically every 3 days but otherwise persists between application launches and reboots.
  
 
== Determining where to store files ==
 
== Determining where to store files ==
 
+
{{macOS file storage}}
Before we go any further, let's recap where the Apple Guidelines indicate your application should store its files:
 
 
 
* Use the [[Multiplatform_Programming_Guide#Proper_macOS_file_locations|Application Support directory]] (this is backed up by Time Machine), appending your <bundle_ID>, for:
 
** Resource and data files that your application creates and manages for the user. You might use this directory to store application state information, computed or downloaded data, or even user created data that you manage on behalf of the user.
 
** Autosave files.
 
 
 
* Use the Caches directory (this is '''not''' backed up by Time Machine), appending your <bundle_ID>, for cached data files or any files that your application can recreate easily.
 
 
 
* Use [[Mac Preferences Read and Write|CFPreferences]] to read and write your application's preferences. This will automatically writes preferences to the appropriate location and read them from the appropriate location. This data is backed up by Time Machine.
 
 
 
* Use NSTemporaryDirectory (this is '''not''' backed up by Time Machine) to store temporary files that you intend to use immediately for some ongoing operation but then plan to discard later. Delete temporary files as soon as you are done with them.
 
  
 
== Storing temporary files ==
 
== Storing temporary files ==
  
The following string support functions are needed for our use of ''NSTemporaryDirectory()'':
+
How to locate the user's unique temporary directory, display it and write a temporary file to it is demonstrated by the code below:
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
// String support functions (source https://macpgmr.github.io)
+
...
 +
{$modeswitch objectivec1}
 +
 
 +
interface
  
 
Uses
 
Uses
...
+
  ...
SysUtils,
+
  CocoaAll;
CocoaAll,
+
...
...;
 
 
 
function CFStrToAnsiStr(cfStr    : CFStringRef;
 
                        encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): AnsiString;
 
{Convert CFString to AnsiString.
 
  If encoding is not specified, encode using CP1252.}
 
var
 
  StrPtr  : Pointer;
 
  StrRange : CFRange;
 
  StrSize  : CFIndex;
 
begin
 
  if cfStr = nil then
 
    begin
 
    Result := '';
 
    Exit;
 
    end;
 
 
 
  {First try the optimized function}
 
  StrPtr := CFStringGetCStringPtr(cfStr, encoding);
 
  if StrPtr <> nil then  {Succeeded?}
 
    Result := PChar(StrPtr)
 
  else  {Use slower approach - see comments in CFString.pas}
 
    begin
 
    StrRange.location := 0;
 
    StrRange.length := CFStringGetLength(cfStr);
 
  
    {Determine how long resulting string will be}
+
procedure TmpFile;
    CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'),
 
                    False, nil, 0, StrSize);
 
    SetLength(Result, StrSize);  {Expand string to needed length}
 
 
 
    if StrSize > 0 then  {Convert string?}
 
      CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'),
 
                      False, @Result[1], StrSize, StrSize);
 
    end;
 
end;  {CFStrToAnsiStr}
 
 
 
function NSStrToAnsiStr(aNSStr  : NSString;
 
                        encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): AnsiString;
 
{Convert NSString to AnsiString.
 
  If encoding is not specified, encode using CP1252.}
 
begin
 
  {Note NSString and CFStringRef are interchangable}
 
  Result := CFStrToAnsiStr(CFStringRef(aNSStr), encoding);
 
end;
 
</syntaxhighlight>
 
 
 
With those string support functions in place, you can now locate the user's unique temporary directory, display it and write a file to it as demonstrated by the code below:
 
 
 
<syntaxhighlight lang="pascal">
 
procedure TForm1.FormShow(Sender: TObject);
 
 
var
 
var
 
   tmpFile: TextFile;
 
   tmpFile: TextFile;
 
   filePath: String;
 
   filePath: String;
 
begin
 
begin
   ShowMessage(NSStrToAnsiStr(NSTemporaryDirectory));
+
   ShowMessage(NSTemporaryDirectory.UTF8String);
  
   filePath := (NSStrToAnsiStr(NSTemporaryDirectory) + PathDelim + 'test.tmp');
+
   filePath := NSTemporaryDirectory.Utf8String + 'test.tmp';
  
 
   AssignFile(TmpFile, filePath);
 
   AssignFile(TmpFile, filePath);
 
   Try
 
   Try
 
     Rewrite(TmpFile);
 
     Rewrite(TmpFile);
     Writeln(TmpFile, 'This is my test tmp file');
+
     Writeln(TmpFile, 'This is my test tmp file.');
 
   Finally
 
   Finally
 
     CloseFile(TmpFile);
 
     CloseFile(TmpFile);
Line 105: Line 48:
  
 
It should be noted that Free Pascal's ''GetTempDir'' function (from SysUtils) uses the value of the macOS TMPDIR environment variable to determine the path to where the current user's temporary files should be stored. This is a possible security issue because the TMPDIR environment variable is easily compromised.
 
It should be noted that Free Pascal's ''GetTempDir'' function (from SysUtils) uses the value of the macOS TMPDIR environment variable to determine the path to where the current user's temporary files should be stored. This is a possible security issue because the TMPDIR environment variable is easily compromised.
 
[[Category:macOS]]
 
[[Category:Code Snippets]]
 

Latest revision as of 08:43, 29 July 2020

English (en)

macOSlogo.png

This article applies to macOS only.

See also: Multiplatform Programming Guide

Overview

To locate the current user's temporary directory in macOS you should use the native macOS Foundation function NSTemporaryDirectory(). This directory is unique for each user, the user is guaranteed to have write permissions to it, and it is in a hashed location which is not predictable in advance so that it is safe from security issues associated with predictable locations. It is also guaranteed to work when your application has been sandboxed.

You should be aware that this directory is cleaned out automatically every 3 days but otherwise persists between application launches and reboots.

Determining where to store files

Before we go any further, let's recap where the Apple Guidelines indicate your application should store its files:

  • Use the /Applications or /Applications/Utilities directory for the application bundle. The application bundle should contain everything: libraries, dependencies, help, every file that the application needs to run except those created by the application itself. If the application bundle is copied to another machine's /Applications or /Applications/Utilities directory, it should be able to run. Installing to these folders requires Admin privileges. The data in these folders is backed up by Time Machine.
  • Use the ~/Applications folder should Admin privileges not be available. This is the standard location for a single user application. This directory should not be expected to exist. The application bundle should contain everything: libraries, dependencies, help, every file that the application needs to run except those created by the application itself. If the application bundle is copied to another machine's /Applications or /Applications/Utilities directory, it should be able to run. This data is backed up by Time Machine.
  • Use the Application Support directory (this data is backed up by Time Machine), appending your <bundle_ID>, for:
    • Resource and data files that your application creates and manages for the user. You might use this directory to store application state information, computed or downloaded data, or even user created data that you manage on behalf of the user.
    • Autosave files.
  • Use the Caches directory (this is not backed up by Time Machine), appending your <bundle_ID>, for cached data files or any files that your application can recreate easily.
  • Use CFPreferences to read and write your application's preferences. This will automatically write preferences to the appropriate location and read them from the appropriate location. This data is backed up by Time Machine.
  • Use the application Resources directory (this is backed up by Time Machine) for your application-supplied image files, sound files, icon files and other unchanging data files necessary for your application's operation.
  • Use NSTemporaryDirectory (this is not backed up by Time Machine) to store temporary files that you intend to use immediately for some ongoing operation but then plan to discard later. Delete temporary files as soon as you are done with them.

Storing temporary files

How to locate the user's unique temporary directory, display it and write a temporary file to it is demonstrated by the code below:

...
{$modeswitch objectivec1} 

interface

Uses
  ...
  CocoaAll;
...

procedure TmpFile;
var
  tmpFile: TextFile;
  filePath: String;
begin
  ShowMessage(NSTemporaryDirectory.UTF8String);

  filePath := NSTemporaryDirectory.Utf8String + 'test.tmp';

  AssignFile(TmpFile, filePath);
  Try
    Rewrite(TmpFile);
    Writeln(TmpFile, 'This is my test tmp file.');
  Finally
    CloseFile(TmpFile);
  End;
end;

Why not use GetTempDir?

It should be noted that Free Pascal's GetTempDir function (from SysUtils) uses the value of the macOS TMPDIR environment variable to determine the path to where the current user's temporary files should be stored. This is a possible security issue because the TMPDIR environment variable is easily compromised.