iPhone OS 4.0 issue with Juce::File

Hello !

I found a little issue with JUCE when I upgraded to the new iPhone SDK 4.0. When I run it on iPhone OS 4 Simulator my app crashes. Basically, it’s working on every OS version excepted 4.0.

So I am switching in debug mode and I see the File API from Juce is no more working well. For example, isDirectory() return me false when the file is a directory, actually. Some dependent methods like existsAsFile() simply give me a beautiful EXC_BAD_ACCESS.

I tried to take a look and I think it’s at this line :

return fullPath.isEmpty() || (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0));

juce_stat(fullPath, indo) may return false, but I’m not sure to understand what is happening sorry.

Hoping to be useful :wink: !

I downloaded JUCE from GIT for about one month.

I’ve not actually installed the 4.0 simulator yet… Will do so this week and try it. Seems very odd to see a problem in the file code, though - functions like stat() are just about the most basic posix calls that exist - all apps will use them. Does the demo app also crash?

I tried your demo and everything seems to work.

I repeat, it’s working on every version under 4.0 (device/simulator iPhone/iPad). It’s quite strange yes.

Ok, I have to apologize. It was a mistake in XCode settings. Sorry for this.

Ok.

Actually, there are several bugs when using File and FileStream Juce API with the new iphone SDK. All of them come from the posix method stat. (I cannot check a directory, cannot instantiate a FileStream, …)

Here is a report from the official apple developers : https://devforums.apple.com/message/253860#253860.

Could be useful.

Thanks.

I can’t follow that link, but there’s a mention in the release notes about it overwriting the buffer that you give it - is that what you mean?

Reply from Eskimo1

[quote]OK. From all the reports I’ve seen this is a simulator only problem. If that’s the case, I have a good explanation of what’s going wrong. Here’s a write up I created for a developer who opened a DTS tech support incident about this:


The Mac OS X kernel, which is what the simulator runs on, supports three of variants of stat, known as ostat, stat, and stat64. For this discussion, ostat is irrelevant; what matters here is the difference between stat and stat64. Specifically, stat64 supports 64-bit inode numbers, which means that the size of (struct stat) has changed.

Obviously we couldn’t just change the size of (struct stat) out from underneath existing Mac binaries, so Mac OS X has a compatibility shim that makes this all work. The System framework implementation of stat expects the 32-bit inode version of (struct stat), and we added another System framework routine, stat$INODE64, that accepts the 64-bit inode version. If you build with a deployment target that guarantees the existence of 64-bit inodes, the compiler turns your call to stat() into a call to stat$INODE64.

This only applies to 32-bit Mac OS X builds. For runtime architectures that have always supported 64-bit inodes, like 64-bit Mac OS X builds and iPhone OS builds, there is only one version of stat, and that automatically gives you 64-bit inodes.

You can learn more about the mechanics of this by reading the extremely convoluted definitions in <sys/stat.h>.

Things get tricky on the simulator, however. There are three cases:

A. When you build with the iPhone OS 3.2 SDK, you compile with the 32-bit inode structure and link to the standard 32-bit stat routine from the System framework, and everything is good.

B. When you build with the iPhone OS 4.0 SDK, you compile with the 64-bit inode structure and link with a special stat routine from a custom System framework that’s part of the iPhone Simulator 4.0 SDK [1]. This stat routine automatically redirects your call to the kernel’s stat64 implementation, and everything is good.

C. The problem occurs when you mix and match, for example, you build with the iPhone OS 4.0 SDK, and thus use the 64-bit inode structure, but run with the iPad simulator, and hence get the Mac OS X System framework, and not the special framework from the iPhone Simulator 4.0 SDK. At this point there’s a mismatch between the structure you’re using (64-bit inodes) and the kernel implementation you’re calling (stat, not stat64), and you get gibberish results.

As far as I can tell, this problem only affects the simulator. iPhone OS has always supported 64-bit inodes, so your devices builds see a fully consistent view of the universe.


I don’t think there’s an easy workaround for this problem. Probably the best solution is to build with the SDK appropriate for the simulator you’re testing with. This does mean that, when building with iPhone SDK 3.2, you’ll need to turn off iOS 4 features at compile time (rather than just at runtime, as you will want to do for your device build).

As, as always, if this represents a significant impediment to your development, make sure you file a bug.
[/quote]

Personally, I used NSFileManager from Core Foundation instead, and it works well.

Hmm, tricky, I can’t really think of a good workaround for that. I don’t really want to rewrite it with NSFileManager because all my stat calls are in code that’s used on both mac and linux…

I am not sure if this helps or is related.

I noticed that when I build with SDK 3.2 even, my file operations always failed. I have found at work around in my case. Here is my code:

	File dir = 
#ifdef JUCE_IPHONE
	File(File::getSpecialLocation(File::userHomeDirectory).getParentDirectory().getFullPathName() + T("Documents/Creator/portableAppSettings/"))
#else
	File(File::getSpecialLocation(File::userDocumentsDirectory).getParentDirectory().getFullPathName() + T("/Creator/portableAppSettings/"))
#endif
	;

The first line of code is what I use on the iPhone build and it works fine. Previously, I used the line at the bottom for all platforms and file operations failed. I looked at the following method in juce_mac_files.mm:

const File File::getSpecialLocation (const SpecialLocationType type)
{
    const ScopedAutoReleasePool pool;

    String resultPath;

    switch (type)
    {
    case userHomeDirectory:
        resultPath = nsStringToJuce (NSHomeDirectory());
        break;

    case userDocumentsDirectory:
        resultPath = "~/Documents";
        break;

Only the userHomeDirectory case calls NSHomeDirectory(). After looking at http://stackoverflow.com/questions/2154576/nshomedirectory-returns-different-path-each-time-iphone-app-is-restarted, I decided to try out my work around and things worked.

My contribution to this issue…Hope it helps someone.

Ok, first of all, here’s a style tip: it’s much better to write your code like this:

File dir = #ifdef JUCE_IPHONE File::getSpecialLocation (File::userHomeDirectory).getSiblingFile ("Documents/Creator/portableAppSettings"); #else File::getSpecialLocation (File::userDocumentsDirectory).getSiblingFile ("Creator/portableAppSettings"); #endif

Always try to avoid mixing strings and files, that’s a recipe for bugs.

But it looks like what you’re doing is actually creating a directory “/var/mobile/Applications/Documents/Creator/etc”, and surely that’s going to hit problems with file permissions, because it’s outside your app’s allocated folder…?

Not true. The “~” gets automatically expanded in the File constructor, so it will be replaced by NSHomeDirectory.

Thanks for the tip Jules - much appreciated!

How do I get create files in my app’s allocated folder?

When I said that I was referring to the home folder - that’s where you’re supposed to put your files… They shouldn’t disappear, even if the path changes.

Sorry to continue asking dumb questions. I was actually creating my folders in the user home directory so I don’t see why that should be a problem. I do have problems when I test the code on device, but no problems on the simulator. Perhaps I misunderstood you and am failing to see the obvious.

Maybe try again to explain what the problem is…?

Ok. Here it goes.

My app discovers devices on an IP-based network. For each device on the network, it creates a folder with the IP address of the device as a name. In this folder, I save device-specific setttings. In some cases, I unzip certain zip files in the device folder. I have a main container folder that I want to create in the user home folder (I call his UnosCreator). So, for example, I want to have the following for a device with the ip address 192.168.5.200:
[*] /<user_home>/creator/192.168.5.200

Now, after fixing the error you indicated, I have the following file write denied message:
sandboxd[417] : Creator(416) deny file-write* /private/var/mobile/Applications/DA08EF16-14EE-42F7-8294-8AD99C67D56E/creator

I imagine that the home directory is being picked up as:
/private/var/mobile/Applications/DA08EF16-14EE-42F7-8294-8AD99C67D56E/

I hope this is clearer. Thanks for any pointers in the right direction.

Some more useful info, the following substitution works as expected on device:

//File::getSpecialLocation (File::userHomeDirectory).getChildFile("creator/portableAppSettings"); File::getSpecialLocation (File::tempDirectory).getChildFile("creator/portableAppSettings");

However, I read online that the temp folder could change each time that the app is started so I won’t be able to persist my application state.

Well, that’s certainly supposed to work. The whole point of that home folder is that it’s somewhere you can write your app’s files. I’ve used it myself, and never had a permissions problem…(?)

I am testing on an iPad but I don’t imagine that is an issue, since it’s all iOS.