Migrating an App to a Sandbox

An app that is not sandboxed places its support files in locations that are inaccessible to a sandboxed version of the same app. For example, whereas a non-sandboxed app might store files in ~/Library/Application Support/<app_name>, the sandboxed version of the same app does not have access to that directory. Instead, the sandbox location for the Application Support directory (obtained using the NSFileManager method URLForDirectory:inDomain:appropriateForURL:create:error:) is within the app’s container, typically found at ~/Library/Containers/<bundle_id>.

If you previously distributed your app without sandboxing and you now want to provide a sandboxed version, you must tell OS X to move your support files into their new, sandbox-accessible locations.

The Migration Process

When a user launches any app, before the app starts running, OS X checks to see if the app is sandboxed. If so, OS X checks to see if a sandbox container directory exists for that app within the user’s Library/Containers folder. If a container directory already exists, then no migration is needed, and the app begins running immediately.

However, if the app’s container directory does not exist, OS X creates it and then migrates the per-user preference file for the app. OS X then carries out any additional migration actions specified in the app's container migration manifest.

A container migration manifest is a special property list file you use to specify certain actions OS X should perform the first time the user launches the sandboxed version of your app. The file must be named container-migration.plist, and must appear in the Contents/Resources directory within your app bundle. You include certain properties to describe the migration actions you wish to perform:

Container migration is a one-way process: You are responsible for providing a way to undo it, should you need to do so during development or testing. The section Undoing a Migration for Testing provides a suggestion about this.

Creating a Container Migration Manifest

To support migration of app support files when a user first launches the sandboxed version of your app, create a container migration manifest.

bullet
To create and add a container migration manifest to an Xcode project
  1. Add a property list file to the Xcode project.

    The Property List template is in the OS X “Resource” group in the file template dialog.

  2. Add a Move property to the container migration manifest.

    The Move property contains an array of files to move into your container directory. This property is usually the lone top-level key in a container migration manifest. You add it to the empty file as follows:

    • Right-click the empty editor for the new .plist file, then choose Add Row.

    • In the Key column, enter Move as the name of the key.

      You must use this exact casing and spelling.

    • In the Type column, choose Array.

  3. Optionally add a Symlink property to the container migration manifest.

    The Symlink property contains an array of files to link into your container directory. The resulting symbolic links in your container directory point to files elsewhere.

  4. Optionally add a MigrateScriptsForApplication property to the container migration manifest.

    The MigrateScriptsForApplication property contains a string or an array of strings naming script directories in ~/Library/Scripts/Applications whose content should be merged into ~/Library/Application Scripts/<bundle_id>.

  5. Add strings to the Move (or Symlink) array for the files or folders you want to migrate.

    For example, suppose you want to migrate your Application Support directory (along with its contained files and subdirectories) to your container. If your directory is named after the app name, for example AppSandboxQuickStart, and is currently within the ~/Library/Application Support directory, use the following string as the value for the new property list item:

    ${ApplicationSupport}/AppSandboxQuickStart

    No trailing slash character is required, and space characters are permitted. The search-path constant in the path is equivalent to ~/Library/Application Support. This constant is described, along with other commonly used directories, in Use Variables to Specify Support-File Directories.

    Add as many strings as needed to identify all the original (before sandboxing) paths of files or folders you want to migrate.

  6. If you are migrating scripts, add a string or array of strings to the MigrateScriptsForApplication property.

    For example, suppose you want to migrate the scripts from the ~/Library/Scripts/Applications/AppSandboxQuickStart directory. In this case, add the following string:

    AppSandboxQuickStart

    As a result of migration, any files in ~/Library/Scripts/Applications/AppSandboxQuickStart are moved to ~/Library/Application Scripts/com.Acme.AppSandboxQuickStart, and the AppSandboxQuickStart directory itself is replaced by a symbolic link to the new location.

Before you first test a migration manifest, provide a way to undo the migration, such as suggested in Undoing a Migration for Testing.

bullet
To test a container migration manifest
  1. In the Finder, open two windows as follows:

    • In one window, view the contents of the ~/Library/Containers/ directory.

    • In the other window, view the contents of the directory containing the support files named in the container migration manifest—that is, the files you want to migrate.

  2. Build and run the Xcode project.

Upon successful migration, the support files disappear from the original (nonsandbox) directory and appear in your app’s container.

If you want to alter the arrangement of support files during migration, use a slightly more complicated .plist structure. Specifically, for a file or directory whose migration destination you want to control, provide both a starting and an ending path. The ending path is relative to the Data directory in your container. In specifying an ending path, you can use any of the search-path constants described in Use Variables to Specify Support-File Directories.

If your destination path specifies a custom directory (one that isn’t part of a standard container), the system creates the directory during migration.

The following task assumes that you’re using the Xcode property list editor and working with the container migration manifest you created earlier in this chapter.

bullet
To control the destination of a migrated file or directory
  1. In the container migration manifest, add a new item to the Move (or Symlink) array.

  2. In the Type column, choose Array.

  3. Add two strings as children of the new array item.

  4. In the top string of the pair, specify the origin path of the file or directory you want to migrate.

  5. In the bottom string of the pair, specify the destination (sandbox) custom path for the file or directory you want to migrate.

File migration proceeds from top-to-bottom through the container migration manifest. Take care to list items in an order that works. For example, suppose you want to move your entire Application Support directory as-is, except for one file. You want that file to go into a new directory parallel to Application Support in the container.

For this approach to work, you must specify the individual file move before you specify the move of the Application Support directory—that is, specify the individual file move higher in the container migration manifest. (If Application Support were specified to be moved first, the individual file would no longer be at its original location at the time the migration process attempted to move it to its new, custom location in the container.)

Undoing a Migration for Testing

When testing migration of support files, you may find it necessary to perform migration more than once. To support this, you need a way to restore your starting directory structures—that is, the structures as they exist prior to migration.

One way to do this is to make a copy of the directories to migrate before you perform a first migration (including, if applicable, any scripts directories affected by the MigrateScriptsForApplication property). Save this copy in a location unaffected by the migration manifest. The following task assumes you have created this sort of backup copy.

bullet
To manually undo a container migration for testing purposes
  1. If you specified MigrateScriptsForApplication in your manifest, remove the symbolic link or links created in ~/Library/Scripts/Applications during migration.

  2. Manually copy the files and directories—those specified in the manifest, including any script directories, if applicable—from your backup copy to their original (premigration) locations. (Be careful not to remove the files from your backup copy as you do so.)

  3. Delete your app’s container.

  4. Delete ~/Library/Application Scripts/<bundle_id>, if it exists.

The next time you launch the app, the system recreates the container and migrates the support files according to the current version of the container migration manifest.

An Example Container Migration Manifest

Listing 4-1 shows an example manifest as viewed in a text editor.

Listing 4-1  An example container migration manifest

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>Move</key>
   <array>
      <string>${Library}/AppSandboxQuickStart/MyConfiguration.plist</string>
      <array>
         <string>${Library}/AppSandboxQuickStart/MyDataStore.xml</string>
         <string>${ApplicationSupport}/AppSandboxQuickStart/MyDataStore.xml</string>
      </array>
   </array>
   <key>MigrateScriptsForApplication</key>
   <string>AppSandboxQuickStart</string>
</dict>
</plist>

This manifest specifies the migration of two items from the user’s Library directory to the app’s container. For the first item, MyConfiguration.plist, only the origin path is specified, leaving it to the migration process to place the file appropriately.

For the second item, MyDataStore.xml, both an origin and a custom destination path are specified.

Finally, the optional MigrateScriptsForApplication property causes any scripts in ~/Library/Scripts/Applications/AppSandboxQuickStart to be moved to ~/Library/Application Scripts/com.Acme.AppSandboxQuickStart, leaving a symbolic link to the new directory in place of the legacy one.

The ${Library} and ${ApplicationSupport} portions of the paths are variables you can use as a convenience. For a list of variables you can use in a container migration manifest, see Use Variables to Specify Support-File Directories.

Use Variables to Specify Support-File Directories

When you specify a path in a container migration manifest, you can use certain variables that correspond to commonly used support file directories. These variables work in origin and destination paths, but the path that a variable resolves to depends on the context. Refer to Table 4-1.

Table 4-1  How system directory variables resolve depending on context

Context

Variable resolves to

Origin path

Home-relative path (relative to the ~ directory)

Destination path

Container-relative path (relative to the Data directory in the container)

The variables you can use for specifying support-file directories are described in Table 4-2. For an example of how to use these variables, see Listing 4-1.

Table 4-2  Variables for use in migration paths

Variable

Meaning

${ApplicationSupport}

The directory containing application support files. Corresponds to the NSApplicationSupportDirectory search-path constant.

${AutosavedInformation}

The directory containing the user’s autosaved documents. Corresponds to the NSAutosavedInformationDirectory search-path constant.

${Caches}

The directory containing discardable cache files. Corresponds to the NSCachesDirectory search-path constant.

${Document}

${Documents}

Each variable corresponds to the directory containing the user’s documents. Corresponds to the NSDocumentDirectory search-path constant.

${Home}

The current user’s home directory. Corresponds to the directory returned by the NSHomeDirectory function. When in a destination path in a manifest, resolves to the container directory.

${Library}

The directory containing application-related support and configuration files. Corresponds to the NSLibraryDirectory search-path constant.

${BundleId}

The app’s bundle identifier, for use in either an origin or destination path.