How do I create an Inter App MIDI In port - objective-c

I will program an inter App MIDI In Port in my Arranger App, that can be accessed by other MIDI App's. I would appreciate very much to get some sample code. I built a virtual MIDI In port like this, but how to make it visible for other App's:
MIDIClientRef virtualMidi;
result = MIDIClientCreate(CFSTR("Virtual Client"), MyMIDINotifyProc, NULL, &virtualMidi);

You need to use MIDIDestinationCreate, which will be visible to other MIDI Clients. You need to provide a MIDIReadProc callback that will be notified when a MIDI event arrives to your MIDI Destination. You may create another MIDI Input Port as well, with the same callback, that you can connect yourself from within your own program to an external MIDI Source.
Here is an example (in C++):
void internalCreate(CFStringRef name)
{
OSStatus result = noErr;
result = MIDIClientCreate( name , nullptr, nullptr, &m_client );
if (result != noErr) {
qDebug() << "MIDIClientCreate() err:" << result;
return;
}
result = MIDIDestinationCreate ( m_client, name, MacMIDIReadProc, (void*) this, &m_endpoint );
if (result != noErr) {
qDebug() << "MIDIDestinationCreate() err:" << result;
return;
}
result = MIDIInputPortCreate( m_client, name, MacMIDIReadProc, (void *) this, &m_port );
if (result != noErr) {
qDebug() << "MIDIInputPortCreate() error:" << result;
return;
}
}
Another example, in ObjectiveC from symplesynth
- (id)initWithName:(NSString*)newName
{
PYMIDIManager* manager = [PYMIDIManager sharedInstance];
MIDIEndpointRef newEndpoint;
OSStatus error;
SInt32 newUniqueID;
// This makes sure that we don't get notified about this endpoint until after
// we're done creating it.
[manager disableNotifications];
MIDIDestinationCreate ([manager midiClientRef], (CFStringRef)newName, midiReadProc, self, &newEndpoint);
// This code works around a bug in OS X 10.1 that causes
// new sources/destinations to be created without unique IDs.
error = MIDIObjectGetIntegerProperty (newEndpoint, kMIDIPropertyUniqueID, &newUniqueID);
if (error == kMIDIUnknownProperty) {
newUniqueID = PYMIDIAllocateUniqueID();
MIDIObjectSetIntegerProperty (newEndpoint, kMIDIPropertyUniqueID, newUniqueID);
}
MIDIObjectSetIntegerProperty (newEndpoint, CFSTR("PYMIDIOwnerPID"), [[NSProcessInfo processInfo] processIdentifier]);
[manager enableNotifications];
self = [super initWithMIDIEndpointRef:newEndpoint];
ioIsRunning = NO;
return self;
}

Ports can't be discovered from the API, but sources and destinations can. You want to create a MIDISource or MIDIDestination so that MIDI clients can call MIDIGetNumberOfDestinations/MIDIGetDestination or MIDIGetNumberOfSources/MIDIGetSource and discover it.
FYI, there is no need to do what you are planning to do on macOS because the IAC driver already does it. If this is for iOS, these are the steps to follow:
Create at least one MIDI Client.
Create a MIDIInputPort with a read block for I/O.
Use MIDIPortConnectSource to attach the input port to every MIDI Source of interest.
[From now, every MIDI message received by the source will come to your read block.]
If you want to resend this data to a different destination, you'll need to have created a MIDIOutputPort as well. Use MIDISend with that port to the desired MIDI Destination.

Related

Why does xQueueReceive throw an unhandled exception (LoadProhibited)?

I'm working on an esp32 FreeRTOS application with two tasks. Its purpose is to take UART messages received from a peripheral device and transmit them via mqtt to a central broker.
The first task reads input from Serial1, processes the contents into a message structure, and adds it to a FreeRTOS queue:
typedef struct {
int length;
char buffer[AZ_EL_MAX_MESSAGE_LENGTH];
} tag_message_t;
void uart_read_task(void * pvParameters){
BaseType_t xStatus;
tag_message_t tag_message;
while(true) {
while(Serial1.available())
{
first_char = Serial1.read();
if (first_char == '+') // Indicates the beginning of a message
{
for(int i = 0; i < AZ_EL_MAX_MESSAGE_LENGTH; i++)
{
message_buffer[i] = Serial1.read();
if (message_buffer[i] == '\n') // End of message received
{
ESP_LOGV(TAG, "Message found: %s", message_buffer);
strncpy(tag_message.buffer, message_buffer, i + 1);
tag_message.length = i + 1;
xStatus = xQueueSend(xMessagesToSendQueue, (void*) &tag_message, 0);
if (xStatus != pdPASS)
ESP_LOGW(TAG, "Failed to queue message.");
break;
}
}
}
}
vTaskDelay(pdMS_TO_TICKS(20)); // Wait the minimum BLE advertisement period for messages to come in, i.e. 20ms
}
}
The main loop() (which is technically the second FreeRTOS task) then attempts to receive from that queue and transmit over MQTT to a local broker:
void setup()
{
Serial.begin(115200);
// Configure and start WiFi
configure_network();
connect_network();
// Configure the MQTT connection
configure_mqtt_client();
// Configure and create the inter-task queues
xMessagesToSendQueue = xQueueCreate(100, sizeof(tag_message_t));
if (xMessagesToSendQueue == NULL) {
ESP_LOGE(TAG, "Unable to create messaging queue. Will not create UART handling message queue.");
delay(10000);
esp_restart();
} else {
ESP_LOGI(TAG, "Messaging queue generated");
configure_uart();
xTaskCreate(uart_read_task, "UART_Processing", 20000, NULL, 1, NULL);
}
}
void loop()
{
const TickType_t xTicksToWait = pdMS_TO_TICKS(100); // milliseconds to wait
tag_message_t received_message;
if (network_connected) {
connect_mqtt_client();
while(mqtt_client.connected())
{
mqtt_client.loop();
// Process messages on the xMessagesToSendQueue
if (xMessagesToSendQueue != NULL)
{
ESP_LOGI(TAG, "Processing message");
if (xQueueReceive(xMessagesToSendQueue, &received_message, xTicksToWait) == pdPASS)
{
ESP_LOGD(TAG, "Received message, transmitting.");
if(!mqtt_client.publish("aoa", received_message.buffer, received_message.length));
ESP_LOGW(TAG, "Failed transmission.");
}
else
{
vTaskDelay(pdMS_TO_TICKS(50));
}
}
else
{
ESP_LOGE(TAG, "Messages queue is null.");
}
}
} else {
ESP_LOGE(TAG, "WARNING Device not connected to the network. Reconnecting.");
connect_network();
}
delay(5000);
}
I've verified that the MQTT broker works, that it connects to WiFi, and it can properly read messages from Serial1. HOWEVER, the xQueueReceive() call in loop() throws a LoadProhibited exception every time it's called.
Can anyone tell me what I'm getting wrong here?
All, thank you for your help. It turns out this wasn't a FreeRTOS issue. After a little research (i.e. reading this and watching a more experienced engineer explain things: [link]https://hackaday.com/2017/08/17/secret-serial-port-for-arduinoesp32/) it turns out ESP32 Serial1 pins are connected to flash memory.
Every time I tried Serial1.read() or Serial1.readBytesUntil() the ESP32 crashed. Turns out reading the flash is taboo?
I replaced '''Serial1.read()''' with '''Serial2.read()''' and others. That fixed everything. Now I'm off to optimizing my queues!
You are trying to receive an address on the Queue without casting it.
2 solutions: You either declare received_message as a Pointer:
tag_message_t* received_message;
...
received_message = new tag_message_t;
if (xQueueReceive(xMessagesToSendQueue, received_message, xTicksToWait) == pdPASS)
and don't forget to delete it right after usage.
Or you can cast it after receiving :
if (xQueueReceive(xMessagesToSendQueue, &received_message, xTicksToWait) == pdPASS)
{
received_message = *(static_cast<tag_message_t*>(&received_message));
ESP_LOGD(TAG, "Received message, transmitting.");
if(!mqtt_client.publish("aoa", received_message.buffer, received_message.length));
ESP_LOGW(TAG, "Failed transmission.");
}
or any other sort of dereferencing you might wanna try.
There is also the possibility xQueueReceive is already doing that! So let's be sure of what is going on by adding this:
ESP_LOGD(TAG, "Received message, transmitting. %s", received_message.buffer);
Right after you get the message.
I don't think tag_message is being deleted inside the task, so if the struct is still valid and present, if you properly parse/cast its address, you should be able to acquire the message without any issues.

Problems connecting to the input pins of GMFBridge Sink Filter

I am experiencing a strange problem when trying to use the GMFBridge filter with the output of an Euresys UxH264 card.
I am trying to integrate this card into our solution, that relies on GMFBridge to handle the ability of continuous capture to multiple files, performing muxing and file-splitting without having to stop the capture graph.
This card captures video and audio from analog inputs. It provides a DirectShow filter exposing both a raw stream of the video input and a hardware-encoded H.264 stream. The audio stream is provided as an uncompressed stream only.
When I attempt to directly connect any of the output pins of the Euresys source filters to the input pins of the GMFBridge Sink, they get rejected, with the code VFW_E_NO_ALLOCATOR. (In the past I have successfully connected both H.264 and raw audio streams to the bridge).
Grasping at straws, I plugged in a pair of SampleGrabber filters between the Euresys card filters and the bridge sink filter, and -just like that- the connections between sample grabbers and sink were accepted.
However, I am not getting any packets on the other side of the bridge (the muxing graph). I inspected the running capture graph with GraphStudioNext and somehow the sample grabbers appear detached from my graph, even though I got successful confirmations when I connected them to the source filter!.
Here's the source code creating the graph.
void EuresysSourceBox::BuildGraph() {
HRESULT hRes;
CComPtr<IGraphBuilder> pGraph;
COM_CALL(pGraph.CoCreateInstance(CLSID_FilterGraph));
#ifdef REGISTER_IN_ROT
_rotEntry1 = FilterTools::RegisterGraphInROT(IntPtr(pGraph), "euresys graph");
#endif
// [*Video Source*]
String^ filterName = "Ux H.264 Visual Source";
Guid category = _GUIDToGuid((GUID)AM_KSCATEGORY_CAPTURE);
FilterMonikerList^ videoSourceList = FilterTools::GetFilterMonikersByName(category, filterName);
CComPtr<IBaseFilter> pVideoSource;
int monikerIndex = config->BoardIndex; // a filter instance will be retrieved for every installed board
clr_scoped_ptr<CComPtr<IMoniker>>^ ppVideoSourceMoniker = videoSourceList[monikerIndex];
COM_CALL((*ppVideoSourceMoniker->get())->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pVideoSource));
COM_CALL(pGraph->AddFilter(pVideoSource, L"VideoSource"));
// [Video Source]
//
// [*Audio Source*]
filterName = "Ux H.264 Audio Encoder";
FilterMonikerList^ audioSourceList = FilterTools::GetFilterMonikersByName(category, filterName);
CComPtr<IBaseFilter> pAudioSource;
clr_scoped_ptr<CComPtr<IMoniker>>^ ppAudioSourceMoniker = audioSourceList[monikerIndex];
COM_CALL((*ppAudioSourceMoniker->get())->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pAudioSource));
COM_CALL(pGraph->AddFilter(pAudioSource, L"AudioSource"));
CComPtr<IPin> pVideoCompressedOutPin(FilterTools::GetPin(pVideoSource, "Encoded"));
CComPtr<IPin> pAudioOutPin(FilterTools::GetPin(pAudioSource, "Audio"));
CComPtr<IBaseFilter> pSampleGrabber;
COM_CALL(pSampleGrabber.CoCreateInstance(CLSID_SampleGrabber));
COM_CALL(pGraph->AddFilter(pSampleGrabber, L"SampleGrabber"));
CComPtr<IPin> pSampleGrabberInPin(FilterTools::GetPin(pSampleGrabber, "Input"));
COM_CALL(pGraph->ConnectDirect(pVideoCompressedOutPin, pSampleGrabberInPin, NULL)); // DOES NOT FAIL!!
CComPtr<IBaseFilter> pSampleGrabber2;
COM_CALL(pSampleGrabber2.CoCreateInstance(CLSID_SampleGrabber));
COM_CALL(pGraph->AddFilter(pSampleGrabber2, L"SampleGrabber2"));
CComPtr<IPin> pSampleGrabber2InPin(FilterTools::GetPin(pSampleGrabber2, "Input"));
COM_CALL(pGraph->ConnectDirect(pAudioOutPin, pSampleGrabber2InPin, NULL)); // DOES NOT FAIL!!
// [Video Source]---
// |-->[*Bridge Sink*]
// [Audio Source]---
CComPtr<IPin> pSampleGrabberOutPin(FilterTools::GetPin(pSampleGrabber, "Output"));
CComPtr<IPin> pSampleGrabber2OutPin(FilterTools::GetPin(pSampleGrabber2, "Output"));
CreateGraphBridge(
IntPtr(pGraph),
IntPtr(pSampleGrabberOutPin),
IntPtr(pSampleGrabber2OutPin)
);
// Root graph to parent object
_ppCaptureGraph.reset(new CComPtr<IGraphBuilder>(pGraph));
}
COM_CALL is my HRESULT checking macro, it will raise a managed exception if the result is other than S_OK. So the connection between pins succeeded, but here is how the graph looks disjointed when it is running:
So, I have three questions:
1) What could VFW_E_NO_ALLOCATOR mean in this instance? (the source filter can be successfully connected to other components such as LAV Video decoder or ffdshow video decoder).
2) Is there a known workaround to circumvent the VFW_E_NO_ALLOCATOR problem?
3) Is it possible that a filter gets disconnected at runtime as it seems to be happening in my case?
I found a reference by Geraint Davies giving a reason as to why the GMFBridge sink filter may be rejecting the connection.
It looks as though the parser is insisting on using its own allocator
-- this is common with parsers where the output samples are merely pointers into the input samples. However, the bridge cannot implement
suspend mode without using its own allocator, so a copy is required.
With this information, I decided to create an ultra simple CTransformFilter filter that simply accepts the allocator offered by the bridge and copies to the output sample whatever comes in from the input sample. I am not 100% sure that what I did was right, but it is working now. I could successfully plug-in the Euresys card as part of my capture infrastructure.
For reference, if anyone experiences something similar, here is the code of the filter I created:
class SampleCopyGeneratorFilter : public CTransformFilter {
protected:
HRESULT CheckInputType(const CMediaType* mtIn) override { return S_OK; }
HRESULT GetMediaType(int iPosition, CMediaType* pMediaType) override;
HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) override { return S_OK; }
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp) override;
HRESULT Transform(IMediaSample *pSource, IMediaSample *pDest) override;
public:
SampleCopyGeneratorFilter();
};
//--------------------------------------------------------------------------------------------------------------------
SampleCopyGeneratorFilter::SampleCopyGeneratorFilter()
: CTransformFilter(NAME("SampleCopyGeneratorFilter"), NULL, GUID_NULL)
{
}
HRESULT SampleCopyGeneratorFilter::GetMediaType(int iPosition, CMediaType* pMediaType) {
HRESULT hRes;
ASSERT(m_pInput->IsConnected());
if( iPosition < 0 )
return E_INVALIDARG;
CComPtr<IPin> connectedTo;
COM_CALL(m_pInput->ConnectedTo(&connectedTo));
CComPtr<IEnumMediaTypes> pMTEnumerator;
COM_CALL(connectedTo->EnumMediaTypes(&pMTEnumerator));
AM_MEDIA_TYPE* pIteratedMediaType;
for( int i = 0; i <= iPosition; i++ ) {
if( pMTEnumerator->Next(1, &pIteratedMediaType, NULL) != S_OK )
return VFW_S_NO_MORE_ITEMS;
if( i == iPosition )
*pMediaType = *pIteratedMediaType;
DeleteMediaType(pIteratedMediaType);
}
return S_OK;
}
HRESULT SampleCopyGeneratorFilter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp) {
HRESULT hRes;
AM_MEDIA_TYPE mt;
COM_CALL(m_pInput->ConnectionMediaType(&mt));
try {
BITMAPINFOHEADER* pBMI = HEADER(mt.pbFormat);
pProp->cbBuffer = DIBSIZE(*pBMI); // format is compressed, uncompressed size should be enough
if( !pProp->cbAlign )
pProp->cbAlign = 1;
pProp->cbPrefix = 0;
pProp->cBuffers = 4;
ALLOCATOR_PROPERTIES actualProperties;
COM_CALL(pAlloc->SetProperties(pProp, &actualProperties));
if( pProp->cbBuffer > actualProperties.cbBuffer )
return E_FAIL;
return S_OK;
} finally{
FreeMediaType(mt);
}
}
HRESULT SampleCopyGeneratorFilter::Transform(IMediaSample *pSource, IMediaSample *pDest) {
HRESULT hRes;
BYTE* pBufferIn;
BYTE* pBufferOut;
long destSize = pDest->GetSize();
long dataLen = pSource->GetActualDataLength();
if( dataLen > destSize )
return VFW_E_BUFFER_OVERFLOW;
COM_CALL(pSource->GetPointer(&pBufferIn));
COM_CALL(pDest->GetPointer(&pBufferOut));
memcpy(pBufferOut, pBufferIn, dataLen);
pDest->SetActualDataLength(dataLen);
pDest->SetSyncPoint(pSource->IsSyncPoint() == S_OK);
return S_OK;
}
Here is how I inserted the filter in the capture graph:
CComPtr<IPin> pAACEncoderOutPin(FilterTools::GetPin(pAACEncoder, "XForm Out"));
CComPtr<IPin> pVideoSourceCompressedOutPin(FilterTools::GetPin(pVideoSource, "Encoded"));
CComPtr<IBaseFilter> pSampleCopier;
pSampleCopier = new SampleCopyGeneratorFilter();
COM_CALL(pGraph->AddFilter(pSampleCopier, L"SampleCopier"));
CComPtr<IPin> pSampleCopierInPin(FilterTools::GetPin(pSampleCopier, "XForm In"));
COM_CALL(pGraph->ConnectDirect(pVideoSourceCompressedOutPin, pSampleCopierInPin, NULL));
CComPtr<IPin> pSampleCopierOutPin(FilterTools::GetPin(pSampleCopier, "XForm Out"));
CreateGraphBridge(
IntPtr(pGraph),
IntPtr(pSampleCopierOutPin),
IntPtr(pAACEncoderOutPin)
);
Now, I still have no idea why inserting the sample grabber instead did not work and resulted in detached graphs. I corroborated this quirk by examining the graphs with Graphedit Plus too. If anyone can offer me an explanation, I would be very grateful indeed.

AudioQueue | Change audio device while audioQueue is running

i am using audio queue for playback and for record on OSX | Mac and have a use case,
Its something like, user may change the Audiodevice ( Input and output both ) while audio queue is running either for playback or for record,
This is what i have done so far,
OSStatus result = noErr;
// get the device list
AudioObjectPropertyAddress thePropertyAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
UInt32 thePropSize;
CFStringRef theDeviceName;
// get the device name
thePropSize = sizeof(CFStringRef);
thePropertyAddress.mSelector = kAudioObjectPropertyName;
thePropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
thePropertyAddress.mElement = kAudioObjectPropertyElementMaster;
// get the name of the device
result = AudioObjectGetPropertyData( (AudioObjectID)input,
&thePropertyAddress, 0, NULL, &thePropSize, &theDeviceName);
if ( result != noErr){
log("Error while getting property");
return;
}
// get the uid of the device
CFStringRef theDeviceUID;
thePropertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
result = AudioObjectGetPropertyData( (AudioObjectID)input,
&thePropertyAddress, 0, NULL, &thePropSize, &theDeviceUID);
try{
XThrowIfError(AudioQueueSetProperty(mQueue,kAudioQueueProperty_CurrentDevice,&theDeviceUID, sizeof(CFStringRef)),"set input device");
}
catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
but its throwing an exception kAudioQueueErr_InvalidRunState , referencesaying its can't be done while queue is running;
Is there any other way to achieve the same ?
Unfortunately there isn't any other way to achieve this (AudioQueue has too few functions at all). You need firstly to stop audio queue, then change its property, and finally start it again.

MIDISend: to play a musical note on iPhone

I am trying to generate a musical note that will play through the iPhone speakers using Objective-C and MIDI. I have the code below but it is not doing anything. What am I doing wrong?
MIDIPacketList packetList;
packetList.numPackets = 1;
MIDIPacket* firstPacket = &packetList.packet[0];
firstPacket->timeStamp = 0; // send immediately
firstPacket->length = 3;
firstPacket->data[0] = 0x90;
firstPacket->data[1] = 80;
firstPacket->data[2] = 120;
MIDIPacketList pklt=packetList;
MIDISend(MIDIGetSource(0), MIDIGetDestination(0), &pklt);
You've got three problems:
Declaring a MIDIPacketList doesn't allocate memory or initialize the structure
You're passing the results of MIDIGetSource (which returns a MIDIEndpointRef) as the first parameter to MIDISend where it is expecting a MIDIPortRef instead. (You probably ignored a compiler warning about this. Never ignore compiler warnings.)
Sending a MIDI note in iOS doesn't make any sound. If you don't have an external MIDI device connected to your iOS device, you need to set up something with CoreAudio that will generate sounds. That's beyond the scope of this answer.
So this code will run, but it won't make any sounds unless you've got external hardware:
//Look to see if there's anything that will actually play MIDI notes
NSLog(#"There are %lu destinations", MIDIGetNumberOfDestinations());
// Prepare MIDI Interface Client/Port for writing MIDI data:
MIDIClientRef midiclient = 0;
MIDIPortRef midiout = 0;
OSStatus status;
status = MIDIClientCreate(CFSTR("Test client"), NULL, NULL, &midiclient);
if (status) {
NSLog(#"Error trying to create MIDI Client structure: %d", (int)status);
}
status = MIDIOutputPortCreate(midiclient, CFSTR("Test port"), &midiout);
if (status) {
NSLog(#"Error trying to create MIDI output port: %d", (int)status);
}
Byte buffer[128];
MIDIPacketList *packetlist = (MIDIPacketList *)buffer;
MIDIPacket *currentpacket = MIDIPacketListInit(packetlist);
NSInteger messageSize = 3; //Note On is a three-byte message
Byte msg[3] = {0x90, 80, 120};
MIDITimeStamp timestamp = 0;
currentpacket = MIDIPacketListAdd(packetlist, sizeof(buffer), currentpacket, timestamp, messageSize, msg);
MIDISend(midiout, MIDIGetDestination(0), packetlist);

Very weird issue with AuthorizationExecuteWithPriveleges in Cocoa

I'm using AuthorizationExecuteWithPriveleges to execute bash commands from my App with admin privilege. I have found really weird issue. Here what I'm using
FILE *pipe=nil;
OSStatus err;
AuthorizationRef authorizationRef;
char *command= "/bin/chmod";
char *args[] = {"644","folderPath", nil};
if(err!=0)
{
err = AuthorizationCreate(nil,
kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults,
&authorizationRef);
}
NSLog(#"test");
err = AuthorizationExecuteWithPrivileges(authorizationRef,
command,
kAuthorizationFlagDefaults,
args,
&pipe);
After calling this function about 40 times, it's starting respond very slowly. And after it is will just die,and freeze application, and I have no idea what is happening to this.It doesn't show the log "test", and doesn't do anything, after calling about 40 times.
It doesn't matter what Bash command or what arguments you are using. It still does the same thing. What is wrong with this ? The reason I'm using this, because my App needs to run on 10.5 as well.
Please if someone have idea, what can I do. I really appreciate it. I need ASAP. Thanks
Looked at this a bit more, and cooked up the following example, presented without warranty, but which works for me for thousands of invocations of AuthorizationExecuteWithPrivileges without issue:
void DoOtherStuff(AuthorizationRef auth, char* path);
void DoStuff(char* path)
{
AuthorizationItem foo;
foo.name = kAuthorizationRightExecute;
foo.value = NULL;
foo.valueLength = 0;
foo.flags = 0;
AuthorizationRights rights;
rights.count = 1;
rights.items = &foo;
AuthorizationRef authorizationRef;
OSStatus err = errAuthorizationSuccess;
if (errAuthorizationSuccess != (err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef)))
{
NSLog(#"Error on AuthorizationCreate: %lu", (long)err);
return;
}
for (NSUInteger i = 0; i < 5000; i++)
{
NSLog(#"Doing run: %lu", (long)i+1);
DoOtherStuff(authorizationRef, "/tmp/foo");
}
if (errAuthorizationSuccess != (err = AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults)))
{
NSLog(#"Error on AuthorizationFree: %lu", (long)err);
return;
}
}
void DoOtherStuff(AuthorizationRef authorizationRef, char* path)
{
OSStatus err = errAuthorizationSuccess;
FILE *pipe = NULL;
#try
{
char *args[] = {"644", path, NULL};
if (errAuthorizationSuccess != (err = AuthorizationExecuteWithPrivileges(authorizationRef,
"/bin/chmod", kAuthorizationFlagDefaults, args, &pipe)))
{
NSLog(#"Error on AuthorizationExecuteWithPrivileges: %lu", (long)err);
return;
}
int stat;
wait(&stat);
NSLog(#"Success! Child Process Died!");
}
#finally
{
if (pipe)
fclose(pipe);
}
}
What Chris Suter said is dead on. What happens when you call AuthorizationExecuteWithPrivileges is that it fork()s your process and then exec()s the requested process (chmod in this case) from the child process. The child process won't be reaped until someone calls wait(), but that's hard because we don't get the PID of the child out of AuthorizationExecuteWithPrivileges (it would have been returned by fork()). As he said, if you're sure there aren't other threads spawning processes at the same time (i.e. your thread is the only one creating child processes), then you can just call the non-PID specific version of wait() like I do in this example.
If you don't call wait() then what happens is you accumulate these zombie child processes that are all waiting to be reaped. Eventually the OS says "no more."
I feel kinda bad posting this, since it's just a retread of what Chris Suter said; I've upvoted his answer.
For completeness, here's a reworked version of that example that achieves the goal by ignoring SIGCHLD instead of calling wait. It also is presented without warranty, but works for me.
void DoOtherStuff(AuthorizationRef auth, char* path);
void DoStuff(char* path)
{
AuthorizationItem foo;
foo.name = kAuthorizationRightExecute;
foo.value = NULL;
foo.valueLength = 0;
foo.flags = 0;
AuthorizationRights rights;
rights.count = 1;
rights.items = &foo;
AuthorizationRef authorizationRef;
OSStatus err = errAuthorizationSuccess;
struct sigaction oldAction;
struct sigaction newAction;
newAction.__sigaction_u.__sa_handler = SIG_IGN;
newAction.sa_mask = 0;
newAction.sa_flags = 0;
if(0 != sigaction(SIGCHLD, &newAction, &oldAction))
{
NSLog(#"Couldn't ignore SIGCHLD");
return;
}
#try
{
if (errAuthorizationSuccess != (err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef)))
{
NSLog(#"Error on AuthorizationCreate: %lu", (long)err);
return;
}
for (NSUInteger i = 0; i < 1000; i++)
{
NSLog(#"Doing run: %lu", (long)i+1);
DoOtherStuff(authorizationRef, "/tmp/foo");
}
if (errAuthorizationSuccess != (err = AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults)))
{
NSLog(#"Error on AuthorizationFree: %lu", (long)err);
return;
}
}
#finally
{
const struct sigaction cOldAction = oldAction;
if(0 != sigaction(SIGCHLD, &cOldAction, NULL))
{
NSLog(#"Couldn't restore the handler for SIGCHLD");
return;
}
}
}
void DoOtherStuff(AuthorizationRef authorizationRef, char* path)
{
OSStatus err = errAuthorizationSuccess;
FILE *pipe = NULL;
#try
{
char *args[] = {"644", path, NULL};
if (errAuthorizationSuccess != (err = AuthorizationExecuteWithPrivileges(authorizationRef,
"/bin/chmod", kAuthorizationFlagDefaults, args, &pipe)))
{
NSLog(#"Error on AuthorizationExecuteWithPrivileges: %lu", (long)err);
return;
}
NSLog(#"Success!");
}
#finally
{
if (pipe)
fclose(pipe);
}
}
What you’re trying to do is not a good idea.
I would guess that you have a bug else where in your code, perhaps in the monitoring of the pipe. We need to see the rest of your code.
If you do pursue this approach, you will need to take care and make sure that you clean up zombie processes which can be awkward when using AuthorizationExecuteWithPrivileges because you don’t get the child process ID. You’ll either need to ignore SIGCHLD, or if you can be certain there are no other threads that are doing things with processes at the same time, you can just issue a call to wait.
You’ve also got to make sure you clean up the pipe as otherwise you’ll run out of file descriptors.
The system is far less forgiving about you leaking file descriptors or processes than it is about leaking memory.
The correct approach for your problem is probably to write a helper tool and then communicate with your helper tool asking it to perform privileged operations on your behalf. That way you’ll only be running your helper tool once. You should be able to read more about this in Apple’s documentation.
You should initialize err (due to the first IF statement), because it's no guaranteed to be 0. However, it probably is, so you are skipping AuthorizationCreate, so the authorized session isn't created.
Basically you are passing authorizationRef uninitialized to AuthorizationExecuteWithPrivileges which might be a problem.
Plus like others, I would put AuthorizationFree(authorizationRef,kAuthorizationFlagDefaults); at the end as well when you do use AuthorizationCreate to free the memory.
Also it's worth noting that AuthorizationExecuteWithPrivileges is deprecated as of OS X v10.7, but I think you know that since you said you are trying to run on 10.5
EDIT: You might want to check the status of err too after running AuthorizationCreate
if ( err != errAuthorizationSuccess ) {
return;
}
... you should check err after AuthorizationExecuteWithPrivileges as well.
I think I might know what's going on here: Try handling the pipe correctly (i.e. don't pass NULL, and make sure you close it). Weird stuff like this also happens with NSTask if you fail to give it a STDIN pipe. This page over at cocoadev.com explains:
An NSTask will break Xcode's debug log entirely if you execute
ANYTHING related with sh or bash (including scripts). printf, NSLog;
all will cease to function as soon as the task has been launched. Even
things like right clicking on an object in the debugger will yield
nothing (straight GDB still prints though). ... I figured out that the
problem lies with standard input, of all things. A quick fix for this
would be to set your standard input up to something random, like a
pipe, and do nothing with it.
This stumped me for hours and hours (albiet with NSTask and not AS). I would be surprised if the odd behavior you're seeing isn't related. Make sure you're not passing NULL, and then make sure that you're cleaning up the file handle that AuthorizationExecuteWithPrivileges creates for you, by calling fclose, etc.
I'm not 100% sure, but the "NSLog stopped working" symptom caught my eye.