Moving Files to the Trash

August 3rd, 2008

On the REALbasic forums, a question involving moving files to the trash came up. The approach taken was to use myFolderItem.moveFileTo(SpecialFolder.trash), but this has some problems. First off, this will move the file across different volumes, resulting in extremely long wait times (and application hangs) for large files. Secondly, it will fail if there is already a file of the same name in the Trash.

To make this task easier, Apple introduced the FSMoveObjectToTrashSync function in Leopard. Prior to Leopard, you had the options of rolling your own (and getting it wrong in subtle ways) or using NSWorkspace’s performFileOperation:source:destination:files:tag:.

To use FSMoveObjectToTrashSync, we’ll need to convert the FolderItem into an FSRef. This is actually fairly trivial, CoreFoundation will take care of all of the messy details for you:

Function GetFSRef(f as FolderItem) as MemoryBlock
  declare function CFURLCreateWithString lib "CoreFoundation" ( allocator as ptr, _
    urlString as CFStringRef, baseURL as ptr ) as ptr
  declare function CFURLGetFSRef lib "CoreFoundation" ( url as ptr, fsRef as ptr ) _
    as boolean
  declare sub CFRelease lib "CoreFoundation" ( obj as ptr )
 
  dim result as new MemoryBlock( 80 )
  dim url as ptr = CFURLCreateWithString( nil, f.urlPath, nil )
  call CFURLGetFSRef( url, result )
  CFRelease( url )
 
  return result
End Function

Then we can just call FSMoveObjectToTrashSync:

Sub MoveToTrash(f as FolderItem)
  declare function FSMoveObjectToTrashSync lib "CoreServices" ( source as ptr, _
    target as ptr, options as integer ) as integer
  const kFSFileOperationDefaultOptions = 0
 
  dim ref as MemoryBlock = getFSRef( f )
  call FSMoveObjectToTrashSync( ref, nil, kFSFileOperationDefaultOptions )
End Sub

Note that in both code snippets, error handling is left as an exercise for the reader.


The Universal Binary of Crashiness

June 9th, 2008

What would you say to an application that crashes Finder to the point of being useless? One that merely has to be downloaded, and not even run? I present NukeApp.

For those who don’t feel like experiencing this firsthand, the behavior will vary depending on your Safari download settings. If you’ve set it automatically open downloaded files, Safari will crash and the world will be happy. If you have that option turned off, and you open the zip file up in Finder, Finder will enter a crash loop.

View a movie of the experience.


So, on to the technical details. I’ll start off by saying that this binary has been specifically crafted and is not a valid universal binary — it lies horribly about the number of architectures it contains.

Let’s take a look at the stack trace for where it dies (only showing the top few frames):

_LSAddExecutableFormatInfo
_LSAddBundleExecutableInfo
_LSRegisterDirectoryNode
_LSFindOrRegisterBundleNode
_LSCopyInfoForNode
_LSCopyItemInfoForRefInfo

Aha, it’s LaunchServices giving us the trouble! It looks like Finder is asking it for information about the application and LaunchServices is registering the application in its database. From the looks of it, it’s trying to determine the format of the executable. A closer look at the top function reveals that it’s opening the file and reading it in:

movl        0xfffffb08(%ebp),%eax
leal        0xfffffd34(%ebp),%edx
movl        $0x00000200,0x08(%esp)
movl        %edx,0xfffffafc(%ebp)
movl        %edx,0x04(%esp)
movl        %eax,(%esp)
calll       read$UNIX2003

You’ll notice it’s only reading 512 (0×200) bytes. A bit after this it checks the first long in the data to see which type of executable it is - Mach-O, Mach-O 64-bit, PEF, or a fat binary. If we go down the function a bit more, you’ll notice there’s some strings for each arch type (ppc, ppc64, i386, x86_64). So, what it must be doing is looping through each fat_arch that follows the fat_header. However, since we’re getting crashes, it must not be making sure that it stops reading at the end of its buffer.

Here’s an example that suffers the same issue (ignoring endian issues):

int file = open("/path/to/binary")
char buffer[512];
 
read(buffer, sizeof(buffer), file);
 
fat_header *header = buffer;
fat_arch *archs = buffer + sizeof(fat_header);
 
for(int i = 0; i < header->nfat_archs; i++) {
	// do something with archs[i]
}

This situation comes up in a few diferent places… so how do they handle it?:

  • CFBundle restricts nfat_archs count to the most that fits in the buffer
  • the kernel loader returns error if the archs exceed the size of its buffer (one page size)

I’ll also note that this is not exploitable (it ONLY reads 512 bytes into a 512 byte buffer) and occurs in every version of OS X we could lay our hands on (10.4, 10.5, etc). You cannot inject code into Finder, the Dock, or any other process that crashes in this routine. It’s simply a crash.

We reported this to Apple February 28th (radar 5771210), and have received no response.


Post updated June 9th, with video and slightly better explanation.


A Bit of RB Trivia: The Answer

April 4th, 2008

So, the question I asked last time was this:
Name a situation when the global App object is nil (while executing user code).

The two answers that were submitted and correct are:

  • Charles Yeomans pointed out that properties in modules are destructed after the global App object has been set to nil. Example project.
  • Ben Johnson went the other way around and noticed that the global App object is nil in your Application subclass’ constructor. Example project.

However, neither of these were the answer I was looking for. What happens is that REALbasic does a bit of initialization before setting the global App object. Part of that initialization is setting up the menubar, which entails creating the MenuItems. If you have a subclass of MenuItem in that menubar, REALbasic invokes your constructor, and at that point, the global App object is nil. Example project.

Congratulations to Charles and Ben, both of which will be receiving a free license to CalendarKit.


A Bit of RB Trivia

March 31st, 2008

There’s a free license to CalendarKit 2 to the first person who can answer this question:

Name a situation when the global App object is nil (while executing user code).

* Offer not applicable to Norman Palardy or past or present REAL Software employees.

Update: We have posted the answers.


Demangling C++ Names

March 26th, 2008

RuntimeException.stack is one of the most useful things to be added to REALbasic. However, we can make it a bit better by demangling any C++ function names that might be in the stack trace.

Function CPPDemangle(name as string) As string
  //! Demangles a C++ function name. Returns an empty string on failure.
  //! Only works on Mac OS X.
 
  #if targetMacOS
      declare function __cxa_demangle lib "/usr/lib/libstdc++.6.dylib" ( name as CString, _
      outBuffer as ptr, outLength as ptr, byref outState as integer ) as CString
    declare sub free lib "System" ( value as CString )
    const kStateSuccess = 0
 
    dim state as integer
    dim demangledString as CString = __cxa_demangle( name, nil, nil, state )
 
    if state = kStateSuccess then
      // create our RB string, then free the string demangle gave us
      dim result as string = demangledString
      free( demangledString )
 
      return result
    end if
  #endif
End Function

Then you can apply this function to the exception’s stack like so:

Function GetErrorStack(err as RuntimeException) As string()
  //! Gets the stack trace for our exception. This differs from
 //! RuntimeException.stack in that it demangles C++ names.
 
  // this can crash in some cases, giving us a completely useless crash log
  // <rb-feedback://keeqghwg>, hopefully this will be fixed someday...
  dim stack() as string = err.stack
  dim cleanedStack() as string
 
  // the order of RB's "for each" isn't defined <rb-feedback://hdcdbgfi>, so we have
  // to use a normal loop
  for i as integer = 0 to stack.ubound
    dim name as string = stack( i )
    dim demangledName as string = cppDemangle( name )
 
    if demangledName <> "" then
      cleanedStack.append( demangledName )
    else
      cleanedStack.append( name )
    end if
  next
 
  return cleanedStack
End Function

So, instead of seeing “__ZN14RuntimeListbox7GetTextE15getTextSelectorll” in an exception trace, you will see “RuntimeListbox::GetText(getTextSelector, long, long)“. Much, much easier on the eyes. :)


Programatically Controlling Spaces

March 22nd, 2008

There’s been a lot of questions on mailing lists about how to control Spaces programatically. So far, there’s been no results from these discussions. Well, fortunately accomplishing this is quite simple, if you’re comfortable using private APIs.

Functions to query the current settings:

CGError CGSGetWorkspace(CGSConnectionID cid, CGSWorkspaceID *outWorkspace);
extern bool CoreDockGetWorkspacesEnabled();
extern void CoreDockGetWorkspacesCount(int *rows, int *columns);

You’ll probably also want to switch spaces. You can accomplish this by using a distributed notification. This tells the Dock to switch spaces, giving you all of the normal animations:

- (void)switchToSpace:(CGSWorkspaceID)spaceNumber {
  // note that the notification is 1 based, but CGSWorkspaceID is zero based!
  [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.switchSpaces"
                                                                 object:[NSString stringWithFormat:@"%i", spaceNumber + 1]];
}

And lastly, you’ll probably want to get notified when things change:

//! Gets called when the current space changes.
void SpaceChangedCallback(CGSNotificationType type, CGSWorkspaceID *workspace, unsigned int dataLength, Controller *self) {
  if(*workspace != kCGSTransitioningWorkspaceID) {
    // do something
  }
}
 
//! Gets called when the user enables or disables Spaces.
- (void)spacesEnabledChanged:(NSNotification *)notification {
  // do something
}
 
//! Start listening for workspace related changes.
- (void)registerForNotifications {
  // listen for when Spaces is enabled or disabled
  [[NSDistributedNotificationCenter defaultCenter] addObserver:self
                                                      selector:@selector(spacesEnabledChanged:)
                                                          name:@"SpacesEnableChange"
                                                        object:nil
                                            suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
 
  // listen for when the current space changes
  CGSRegisterNotifyProc((CGSNotifyProcPtr)SpaceChangedCallback, kCGSNotificationWorkspaceChanged, self);
}

You can find a full code example in the CGSInternal svn repository under Examples/SpacesSwitchingMenu, which mimics the Spaces menu extra.

Happy hacking!


Another CKFrameworks Teaser

March 9th, 2008

I spent a bit of time this weekend working on CKFrameworks, so I figured I’d give another teaser. This time I’ll show off the CFType support…

// load our bundle
dim myFile as FolderItem = desktopFolder.child( "test.ape" )
dim bundle as CFTypeRef = CKFrameworks.CFBundleCreate( myFile )
 
// do some random stuff with it
bundle.loadExecutable()
msgbox( bundle.identifier )

Or perhaps you want to get time zone information:

dim timezone as CFTypeRef = CKFrameworks.CFTimeZoneCreateWithName( "EST", true )
msgbox( timezone.name )

As you can see, this is all pure magic. I’m hoping to have an alpha to show at REAL World, so be there or miss out.


Legally Insane

March 8th, 2008

You probably all are aware that Apple released the iPhone SDK a couple of days ago.

We’ve played around with it and definitely intend on developing for it. Ryan and I are getting iPod Touches, and Jon already has his iPhone. :)

Sadly, we can’t say too much about the SDK itself, because the whole thing is under NDA. That’s right, even though anyone can download it and use it, nobody is allowed to talk about it.

Oh well, lawyers are such silly things.


Moral of the Story (Part 2ish)

March 5th, 2008

On a somewhat related topic from last time, I thought I’d mention some other nasty hacks I’ve seen passed off as safe.

Pre-emptive Threading

There are several libraries that claim to offer this in REALbasic. The problem is that it can’t be done. The RB runtime simply isn’t thread safe, not to mention the framework itself!

Something as trivial as referencing a string has the potential to bring down your whole application in bizarre and difficult-to-detect ways. Even if your code doesn’t directly touch the runtime, the code the compiler generates might. The rules on when it does so (to yield to another thread, for example) are undocumented and extremely likely to change from version to version.

To make a long story short: it’s a bad idea.

Weak References

Another library implemented weak references in a horrible manner. They simply unlocked the object and returned it, decrementing its reference count.

At first glance this would appear to work - your object goes away even if another object holds a weak reference to it. However, the problem occurs if you attempt to access the “weak reference” after its original object goes away. This will almost certainly crash your program.

The problem is that your “weak reference” still points to the place in memory where the object lived. By the time you use your “weak reference,” REALbasic has most likely already tossed something else in that location.

To make a long story short: it’s a bad idea.


Moral of the Story

March 3rd, 2008

Back January 2006, I posted a neat trick to get the address of an object. This was on a mailing list full of people who knew what we were doing. We understood that these were hacks and that they shouldn’t be used in actual software.

However, some people were still concerned:

I’m also worried that you guys will write libraries based on this technique, understanding the risk, but will then release them for the use of other RB users, who might not know what they’re getting into.

Mars Saxman

Sadly, this is exactly what’s happened. Someone has taken this trick and used it a library they released. Now everyone who uses that library is relying on this hack never to break.

It’s even worse because the developer of that library didn’t even need this hack. There are better, legal ways to do this. The only reason I can see that he uses the hack is to cut corners.

Now, if upgrading to a new version of REALbasic breaks everyone’s software that uses this hack… who’s going to get blamed? Probably REAL Software and not the library author.

Anyways, the moral of the story is to use hacks responsibly.