zemelenda
zemelenda

Reputation: 89

Is it possible to create hard links to files in an iOS application bundle?

In order to save on server side bandwidth costs associated with my iOS application, I've packaged a bunch of assets that would otherwise be downloadable at runtime into my iOS application bundle. In the context of the application as written, it would be ideal for me if I could access the files from one of the user writeable directories (e.g. [App Dir]/Library/Application Support/My Custom Subfolder/) without having to copy the files there directly at runtime (e.g. at startup, first run, whatever).

While I've been able to successfully create symbolic links in .../My Custom Subfolder/ to the files in the bundle using the NSFileManager API createSymbolicLinkAtURL:withDestinationURL:error:, some of the framework APIs I then use to access the content later on get goofed up and give me back attributes and data pertaining to the symbolic links instead of the underlying file. I could probably mitigate those issues by utilizing some other framework APIs but it might end up being a lot of work depending on the scope of the incorrect usages.

On the simulator I was able to successfully circumvent this issue by creating hard links to the bundle content using the NSFileManager API linkItemAtURL:toURL:error:. The hard links worked great for all the file access APIs utilized throughout the app and everything was peachy. On DEVICE however (tested on iPhone 5c running iOS 7.0.2 and iPad running iOS 7.1), I would receive an NSCocoaErrorDomain 513 error (Operation could not be completed. Operation not permitted.). I could create a test file in .../My Custom Subfolder/ and create a hard link to that in the same folder just fine, but if I try to hardlink to anything in the read-only application bundle, I get the 513 error.

Does anyone know if there's a way to get around the permissions error in order to accomplish what I'm trying to do?

Upvotes: 8

Views: 3281

Answers (1)

benjist
benjist

Reputation: 2881

You simply can't circumvent this error, I'm sure about that. I did not find any official docuentation - but several tutorials explaining how to allow the usage of symbolic links on jailbroken devices. There is no reason to me why the security on iOS is any different for hard links. In the simulator this works because it is operating on a path on your local machine (in ~/Library/Application Support/iphone Simulator/iOSVersion/Applications/...) and with local security. As an counter example, it occurred to me more than once that I had something working in the simulator that didn't work on the device and vice versa (for an example when trying to open a sqlite3 db in the main bundle with SQLITE_WRITE flag).

I resolve this resource management / versioning issue in my apps by using two different file locations. The original versions are inside the main bundle as usual. The other location is either the cache dir or the documents dir. Then I use a custom resource handler which returns a full path for a provided relative path. It works simply like so:

  • If a relative path exists in the writeable dir, return this one. This is supposed to be an updated version of the requested relative file path
  • If the previous check fails, look it up in the main bundle directory to return the original version from the app's main bundle

Some code (using the cache dir as the updateable directory, you may want to change that):

#define UpdateableCacheResourcePath(path) \
[[ResourceHandler sharedManager] updateableCacheResourcePath:path]

- (NSString*)updateableCacheResourcePath:(NSString *)path
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory , NSUserDomainMask, YES);
    NSString *_libraryCacheStorageDir = [[paths objectAtIndex:0] copy];
    NSString *updateablePath = [_libraryCacheStorageDir stringByAppendingPathComponent:path];
    // Cache Dir
    if ([_fileManager fileExistsAtPath:updateablePath]) {
        return updateablePath;
    }

    // mainBundle
    NSString *mainbundleResourcePath = [_mainBundlePath stringByAppendingPathComponent:path];
    if ([_fileManager fileExistsAtPath:mainbundleResourcePath]) {
        return mainbundleResourcePath;
    }

    NSLog(@"File not found: %@", path);
    return nil;
}

In your code you would then use UpdateableCacheResourcePath("some.file") instead of looking in the main bundle. It also works with refrenced folders (drag drop folder to xcode and choose to create a reference instead of a group). Then you can reference relative paths with parent folders (like advertisements/adbanner.png).

The only drawback is that if you have web content you need to update all files that are referenced by your html site for obvious reasons (the web browser can only follow relative paths to the folder where the html document is inside).

Upvotes: 4

Related Questions