GEARZ.de

All snippets are without any warranty.
Alle Snippets sind ohne jegliche Gewähr oder Garantie.



tmDisk.m:   <back to snippets>   <back to howtos>

 /* 
* File: tmDisk.m
*
* gcc -o tmDisk tmDisk.m -framework Foundation -framework Carbon -framework DiskArbitration
* 
* Copyright (C) 2010 Florian Wobbe
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*/

#import <Carbon/Carbon.h>
#import <CoreFoundation/CFPreferences.h>

#import <stdio.h>
#import <libgen.h>
#import <errno.h>

#import <syslog.h>

#import <sys/param.h>
#import <sys/mount.h>
#import <sys/sysctl.h>

// version
#define kVersionCString "2010-11-18 12:13:10"

// process name of time machine daemon
#define kBackupdProcessNameCString "backupd"

// time machine preference list
#define kDestinationVolumeUUIDCFStringRef CFSTR("DestinationVolumeUUID") // MacOSX version < 10.5.6
#define kDestinationVolumeUUIDCFStringRef1065 CFSTR("DestinationUUID")   // MacOSX version >= 10.5.6
#define kBackupAliasCFStringRef CFSTR("BackupAlias")
#define kTMPlistPathCFStringRef CFSTR("/Library/Preferences/com.apple.TimeMachine.plist")

// function definition
int changeTimeMachinePlist(char *filepath);
CFPropertyListRef createMyPropertyListFromFile(CFURLRef fileURL);
void writeMyPropertyListToFile(CFPropertyListRef propertyList,
                           CFURLRef fileURL);
CFStringRef destinationVolumeUUIDKeyCFStringRef ();
CFStringRef getUUIDFromPath (char *filepath);
void abortOnRunningTM ();

// some of this code is adapted from George Warner\'s dockit.c
// make changes to the property list of time machine to reflect new backup volume identifiation
int changeTimeMachinePlist(char *filepath) {
Boolean           isAlias, isFolder;
FSRef             appFSRef;
AliasHandle       myAliasH;
OSStatus          anErr = noErr;
CFDataRef         tCFDataRef;

CFPropertyListRef propertyList;
CFURLRef          fileURL;
CFStringRef       uuid;

// Make sure path doesn\'t end with "/"
// not sure if this is necessary
size_t sizeOfPath = strlen(filepath);
if ( sizeOfPath == 1 && filepath[sizeOfPath-1] == \'/\' ) {
// don\'t backup to root volume
fprintf(stderr, "Cannot select root volume.\n");
syslog(LOG_ALERT, "Cannot select root volume.");
exit(3);
}
else if ( filepath[sizeOfPath-1] == \'/\' )
filepath[sizeOfPath-1] = \'\0\';

// Make an FSRef for the file to be added
anErr = FSPathMakeRef(filepath, &appFSRef, NULL);
if (anErr == fnfErr) {
fprintf(stderr, "File not found.\n");
exit(1);
} else if (anErr != noErr) {
fprintf(stderr, "Trouble with the file. FSPathMakeRef error %d.\n", (int)anErr);
exit(1);
}

// Make sure the file isn\'t an alias, and if it is resolve it.
anErr = FSIsAliasFile (&appFSRef, &isAlias, &isFolder);
if (anErr) {
fprintf(stderr, "Error checking if the file is an alias or not.\n");
exit(1);
}
if (isAlias) {
anErr = FSResolveAliasFileWithMountFlags(&appFSRef, TRUE, &isFolder, &isAlias, kResolveAliasFileNoUI);
if (anErr) {
  fprintf(stderr, "Alias could not be resolved (FSResolveAliasFileWithMountFlags, the alias probably points to a non-mounted file system).\n");
  exit(1);
}
}

// get Device UUID from filepath
uuid = getUUIDFromPath(filepath);

// Create the BackupAlias string for the new backup volume
if (noErr != (anErr = FSNewAlias(NULL,&appFSRef,&myAliasH))) {
fprintf(stderr, "BackupAlias could not be generated (FSNewAlias error: %d).\n", (int)anErr);
exit(1);
}
tCFDataRef = CFDataCreate(kCFAllocatorDefault,(UInt8*) *myAliasH,GetHandleSize((Handle) myAliasH));
if (NULL == tCFDataRef) {
fprintf(stderr, "BackupAlias could not be generated (CFDataCreate failed, tCFDataRef == NULL).\n");
exit(1);
}

// make TM plist file url
fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                                      kTMPlistPathCFStringRef, // file path name
                                      kCFURLPOSIXPathStyle,    // interpret as POSIX path
                                      false );                 // is it a directory?

// read TM plist file
propertyList = createMyPropertyListFromFile(fileURL);
if ( propertyList == NULL ) {
fprintf(stderr, "Error reading TimeMachine property list file.\n");
exit(1);
}

// replace BackupAlias and DestinationVolumeUUID values in plist with new entries
// why do i get this warning?: passing argument 1 of \'CFDictionaryReplaceValue\' discards qualifiers from pointer target type
CFDictionaryReplaceValue(propertyList, destinationVolumeUUIDKeyCFStringRef, uuid);
CFDictionaryReplaceValue(propertyList, kBackupAliasCFStringRef, tCFDataRef);

// precautious: check again for running backupd
abortOnRunningTM();

// save changes to TM plist file
writeMyPropertyListToFile(propertyList, fileURL);

// clean up the mess
CFRelease(propertyList);
CFRelease(uuid);
CFRelease(tCFDataRef);
CFRelease(fileURL);

return 0;
}

// read property list from file url and return the dictionary
CFPropertyListRef createMyPropertyListFromFile(CFURLRef fileURL) {
CFPropertyListRef propertyList = NULL;
CFStringRef       errorString;
CFDataRef         resourceData;
Boolean           status;
SInt32            errorCode;

// Read the XML file.
status = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
                                                fileURL,
                                                &resourceData, // place to put file data
                                                NULL,
                                                NULL,
                                                &errorCode);

if ( ! status ) {
fprintf(stderr, "Error reading TimeMachine property list file: %d\n",
        (int)errorCode);
exit(1);
}

// Reconstitute the dictionary using the XML data.
propertyList = CFPropertyListCreateFromXMLData(kCFAllocatorDefault,
                                             resourceData,
                                             kCFPropertyListMutableContainersAndLeaves,
                                             &errorString);

CFRelease(resourceData);
return propertyList;
}

// write property list to file url
void writeMyPropertyListToFile(CFPropertyListRef propertyList,
                           CFURLRef fileURL) {
CFDataRef xmlData;
Boolean status;
SInt32 errorCode;

// Convert the property list into XML data.
xmlData = CFPropertyListCreateXMLData(kCFAllocatorDefault, propertyList);

// Write the XML data to the file.
status = CFURLWriteDataAndPropertiesToResource (fileURL,                  // URL to use
                                              xmlData,                  // data to write
                                              NULL,
                                              &errorCode);

if ( ! status ) {
fprintf(stderr, "Error cannot write to TimeMachine property list file: %d\n",
        (int)errorCode);
exit(1);
}

CFRelease(xmlData);  
}

// get the right key name for VolumeUUID depending on MacOSX version
CFStringRef destinationVolumeUUIDKeyCFStringRef () {
SInt32 MacVersion;
if (Gestalt(gestaltSystemVersion, &MacVersion) == noErr) {
if (MacVersion < 0x1065) {
  // MacOSX version < 10.5.6
  return kDestinationVolumeUUIDCFStringRef;
}
else {
  // MacOSX version >= 10.5.6
  return kDestinationVolumeUUIDCFStringRef1065;
}
}

fprintf(stderr, "Cannot determine MacOSX version.\n");
exit(1);
}

// get Device UUID from filepath
CFStringRef getUUIDFromPath (char *filepath) {
struct statfs     tStats;
DADiskRef         disk = NULL;
DASessionRef      session = NULL;
CFDictionaryRef   descDict = NULL;
CFUUIDRef         uuid = NULL;
CFStringRef       uuidstring = NULL;

// get file system statistics from path
if ( statfs(filepath, &tStats) == -1 ) {
fprintf(stderr, "Error reading file system statistics: %s\n",
        strerror(errno));
exit(1);
}

session = DASessionCreate(NULL);

if (session != NULL)
// tStats.f_mntfromname holds bsd device name
disk = DADiskCreateFromBSDName(NULL, session, tStats.f_mntfromname);

if (disk != NULL)
descDict = DADiskCopyDescription(disk);

if (descDict != NULL)
// query UUID
uuid = CFDictionaryGetValue(descDict, kDADiskDescriptionVolumeUUIDKey);

if (uuid == NULL) {
fprintf(stderr, "Device UUID could not be determined. Is this a HFS volume?\n");
exit(1);
}
uuidstring = CFUUIDCreateString(NULL, uuid);

// clean up
if (disk != NULL) {
CFRelease(disk);
}
if (session != NULL) {
CFRelease(session);
}

return uuidstring;
}

// adapted from Chad Jones\'s GetPID.c
// Upon successful completion, a value of 0 is returned if no backupd
// is running or 1 if backupd is running. Otherwise, -1 is returned
// and the global variable errno is set to indicate the error.
void abortOnRunningTM () {
// name of time machine process
const char* ProcessName = kBackupdProcessNameCString;

// --- Defining local variables for this function and initializing all to zero --- //
int mib[6] = {0,0,0,0,0,0}; //used for sysctl call.
int SuccessfullyGotProcessInformation;
size_t sizeOfBufferRequired = 0; //set to zero to start with.
int error = 0;
long NumberOfRunningProcesses = 0;
unsigned int Counter = 0;
struct kinfo_proc* BSDProcessInformationStructure = NULL;
pid_t CurrentExaminedProcessPID = 0;
char* CurrentExaminedProcessName = NULL;

//--- Getting list of process information for all processes --- //

/* Setting up the mib (Management Information Base) which is an array of integers where each
* integer specifies how the data will be gathered.  Here we are setting the MIB
* block to lookup the information on all the BSD processes on the system.  Also note that
* every regular application has a recognized BSD process accociated with it.  We pass
* CTL_KERN, KERN_PROC, KERN_PROC_ALL to sysctl as the MIB to get back a BSD structure with
* all BSD process information for all processes in it (including BSD process names)
*/
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_ALL;

/* Here we have a loop set up where we keep calling sysctl until we finally get an unrecoverable error
* (and we return) or we finally get a succesful result.  Note with how dynamic the process list can
* be you can expect to have a failure here and there since the process list can change between
* getting the size of buffer required and the actually filling that buffer.
*/
SuccessfullyGotProcessInformation = FALSE;

while (SuccessfullyGotProcessInformation == FALSE) {
/* Now that we have the MIB for looking up process information we will pass it to sysctl to get the 
 * information we want on BSD processes.  However, before we do this we must know the size of the buffer to 
 * allocate to accomidate the return value.  We can get the size of the data to allocate also using the 
 * sysctl command.  In this case we call sysctl with the proper arguments but specify no return buffer 
 * specified (null buffer).  This is a special case which causes sysctl to return the size of buffer required.
 *
 * First Argument: The MIB which is really just an array of integers.  Each integer is a constant
 *     representing what information to gather from the system.  Check out the man page to know what
 *     constants sysctl will work with.  Here of course we pass our MIB block which was passed to us.
 * Second Argument: The number of constants in the MIB (array of integers).  In this case there are three.
 * Third Argument: The output buffer where the return value from sysctl will be stored.  In this case
 *     we don\'t want anything return yet since we don\'t yet know the size of buffer needed.  Thus we will
 *     pass null for the buffer to begin with.
 * Forth Argument: The size of the output buffer required.  Since the buffer itself is null we can just
 *     get the buffer size needed back from this call.
 * Fifth Argument: The new value we want the system data to have.  Here we don\'t want to set any system
 *     information we only want to gather it.  Thus, we pass null as the buffer so sysctl knows that 
 *     we have no desire to set the value.
 * Sixth Argument: The length of the buffer containing new information (argument five).  In this case
 *     argument five was null since we didn\'t want to set the system value.  Thus, the size of the buffer
 *     is zero or NULL.
 * Return Value: a return value indicating success or failure.  Actually, sysctl will either return
 *     zero on no error and -1 on error.  The errno UNIX variable will be set on error.
 */ 
error = sysctl(mib, 3, NULL, &sizeOfBufferRequired, NULL, (size_t)NULL);

/* If an error occurred then return the accociated error.  The error itself actually is stored in the UNIX 
 * errno variable.  We can access the errno value using the errno global variable.  We will return the 
 * errno value as the sysctlError return value from this function.
 */
if (error != 0) {
  fprintf(stderr, "Error determining if backupd is running: %s\n",
          strerror(errno));
  exit(1);
}

/* Now we successful obtained the size of the buffer required for the sysctl call.  This is stored in the 
 * SizeOfBufferRequired variable.  We will malloc a buffer of that size to hold the sysctl result.
 */
BSDProcessInformationStructure = (struct kinfo_proc*) malloc(sizeOfBufferRequired);

if (BSDProcessInformationStructure == NULL) {
  fprintf(stderr, "Error determining if backupd is running: %s\n",
          strerror(errno));
  exit(1); //unrecoverable error (no memory available) so give up
}

/* Now we have the buffer of the correct size to hold the result we can now call sysctl
 * and get the process information.  
 *
 * First Argument: The MIB for gathering information on running BSD processes.  The MIB is really 
 *     just an array of integers.  Each integer is a constant representing what information to 
 *     gather from the system.  Check out the man page to know what constants sysctl will work with.  
 * Second Argument: The number of constants in the MIB (array of integers).  In this case there are three.
 * Third Argument: The output buffer where the return value from sysctl will be stored.  This is the buffer
 *     which we allocated specifically for this purpose.  
 * Forth Argument: The size of the output buffer (argument three).  In this case its the size of the 
 *     buffer we already allocated.  
 * Fifth Argument: The buffer containing the value to set the system value to.  In this case we don\'t
 *     want to set any system information we only want to gather it.  Thus, we pass null as the buffer
 *     so sysctl knows that we have no desire to set the value.
 * Sixth Argument: The length of the buffer containing new information (argument five).  In this case
 *     argument five was null since we didn\'t want to set the system value.  Thus, the size of the buffer
 *     is zero or NULL.
 * Return Value: a return value indicating success or failure.  Actually, sysctl will either return 
 *     zero on no error and -1 on error.  The errno UNIX variable will be set on error.
 */ 
error = sysctl(mib, 3, BSDProcessInformationStructure, &sizeOfBufferRequired, NULL, (size_t)NULL);

//Here we successfully got the process information.  Thus set the variable to end this sysctl calling loop
if (error == 0)
  SuccessfullyGotProcessInformation = TRUE;
else {
  /* failed getting process information we will try again next time around the loop.  Note this is caused
   * by the fact the process list changed between getting the size of the buffer and actually filling
   * the buffer (something which will happen from time to time since the process list is dynamic).
   * Anyways, the attempted sysctl call failed.  We will now begin again by freeing up the allocated 
   * buffer and starting again at the beginning of the loop.
   */
  free(BSDProcessInformationStructure);
}
}//end while loop

// --- Going through process list looking for processes with matching names --- //

/* Now that we have the BSD structure describing the running processes we will parse it for the desired
* process name.  First we will the number of running processes.  We can determine
* the number of processes running because there is a kinfo_proc structure for each process.
*/
NumberOfRunningProcesses = sizeOfBufferRequired / sizeof(struct kinfo_proc);  

/* Now we will go through each process description checking to see if the process name matches that
* passed to us.  The BSDProcessInformationStructure has an array of kinfo_procs.  Each kinfo_proc has
* an extern_proc accociated with it in the kp_proc attribute.  Each extern_proc (kp_proc) has the process name
* of the process accociated with it in the p_comm attribute and the PID of that process in the p_pid attibute.
* We test the process name by compairing the process name passed to us with the value in the p_comm value.
* Note we limit the compairison to MAXCOMLEN which is the maximum length of a BSD process name which is used
* by the system. 
*/
for (Counter = 0 ; Counter < NumberOfRunningProcesses ; Counter++) {
//Getting PID of process we are examining
CurrentExaminedProcessPID = BSDProcessInformationStructure[Counter].kp_proc.p_pid; 

//Getting name of process we are examining
CurrentExaminedProcessName = BSDProcessInformationStructure[Counter].kp_proc.p_comm;

if ((CurrentExaminedProcessPID > 0) //Valid PID
    && ((strncmp(CurrentExaminedProcessName, ProcessName, MAXCOMLEN) == 0))) //name matches
{
  fprintf(stderr, "backupd is running. Wait for current backup to finish.\n");
  syslog(LOG_ALERT, "backupd is running. Wait for current backup to finish.");
  exit(2);
}
}//end looking through process list

free(BSDProcessInformationStructure); //done with allocated buffer so release.

//didn\'t find any matches return error.
}

int main(int argc, char *argv[]) {

// check arguments
if ( argc < 2 ) {
fprintf(stderr,
        "tmDisk | current version: %s\n"
        "Usage: %s <volume path>\n",
        kVersionCString, basename(argv[0]));
exit(1);
}

// test for running TM process
abortOnRunningTM();

// change time machine volume
changeTimeMachinePlist(argv[1]);

return 0;
}