All snippets are without any warranty.
Alle Snippets sind ohne jegliche Gewähr oder Garantie.
/*
* 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;
}