Blog Post

Tips For macOS Programming - Part 1

Critical sections, reader/writer locks, interactive login sessions, root user detection, CFString conversion.

Tips For macOS Programming - Part 1 - Critical sections, reader/writer locks, interactive login sessions, root user detection, CFString conversion.

Intro

Apple's Xcode would probably be at the bottom of my list of IDEs these days, far behind in its usefulness, versatility and user-friendliness. But, if you're stuck dealing with it (say, when you are developing an Apple-only binary) I will share a few useful tricks that may come handy on your way.

In my particular example I will be focusing on developing for macOS itself, as it is still somewhat less limiting than developing for iOS and similar devices. But many of these tricks could apply to other hardware as well.

I will also base my advice on developing using C (and C++) programming languages, since the former one can be easily incorporated into the higher-level language prioritized by Apple these days, such as Swift.

The full source code for the code samples that I used below could be found in my GitHub repo.

Without further adieu, let's begin. In no particular order.

Contents

This is one of those blog posts that needs its own table of contents:

Critical Sections

Windows has long used critical sections (as opposed to mutexes) to provide fast user-mode means of attempting to resolve synchronization locks in the same process, as opposed to entering the kernel mode. The latter one involves a heavy overhead of crossing the user-mode/kernel security boundary.

On macOS though, we'll have to resort to using Apple's Darwin mutex, that acts more or less like Microsoft's critical section.

Note that the alternative to using *nix's pthread_mutex_lock would be Apple's OSSpinLock or its newer counterpart OSAllocatedUnfairLock. There's one big caveat though. Those functions do not support reentrancy.

I'll put it all into a class for better usability, but if you don't have access to it, just introduce the init() and deinit() functions instead of the constructor and destructor:

C++[Copy]
#include <pthread.h>
#include <assert.h>

struct CRITICAL_SECTION
{
    CRITICAL_SECTION()
    {
        if(_init_CS(_cs) != 0)
        {
            //Failed to initialize - abort!
            assert(false);
			abort();
        }
    }
    
    ~CRITICAL_SECTION()
    {
        pthread_mutex_destroy(&_cs);
    }
    
	///Acquire a critical section lock
	///INFO: This function does not return until the lock is available.
	///      This function supports reentrancy, or calling it repeatedly
	///      from the same thread without blocking.
    void EnterCriticalSection()
    {
        if(pthread_mutex_lock(&_cs) != 0)
        {
            //Failed to enter - abort!
            assert(false);
			abort();
        }
    }
    
	///Leave a previously acquired critical section lock
	///IMPORTANT: You must have called EnterCriticalSection() previously
	///           the same number of times that you call this function!
    void LeaveCriticalSection()
    {
        if(pthread_mutex_unlock(&_cs) != 0)
        {
            //Failed to leave - abort!
			//There's either a failure in logic in the code that calls this function, or
			//there's some memory corruption...
            assert(false);
			abort();
        }
    }
    
private:
    
    ///Initialize multi-entrant critical section
    ///RETURN:
    ///     - 0 if success, otherwise error number
    int _init_CS(pthread_mutex_t& cs)
    {
        pthread_mutexattr_t attr;
        int r;

        r = pthread_mutexattr_init(&attr);
        if(r != 0)
        {
            //Error
            return r;
        }
        
        //Set mutex to be recursive
        r = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

        if(r == 0)
        {
            //Init mutex
            r = pthread_mutex_init(&cs, &attr);
            if(r != 0)
            {
                //Error
                assert(false);
            }
        }
        else
        {
            //Error
            assert(false);
        }

        //Free attributes
        pthread_mutexattr_destroy(&attr);

        return r;
    }
    
    
private:
    ///Copy constructor and assignments are NOT available!
    CRITICAL_SECTION(const CRITICAL_SECTION& s) = delete;
    CRITICAL_SECTION& operator = (const CRITICAL_SECTION& s) = delete;
    
private:
    
    pthread_mutex_t _cs = {};
};

Note that a critical section should not fail by definition. We asked to enter it, and if the lock is not available, we wait until it becomes available. So if one of the underlying mutex functions in the code above fails, we're dealing with some really bad situation, and thus the only appropriate resolution in that case is to crash the process using the abort function. The diagnosis in that case can be obtained by studying the crash dump file.

In case the class above fails, you most certainly have one of the two scenarios:

  1. You have a memory corruption that messed up the internal mutex data structure.
  2. The logic in your app is broken. Say, if you're calling CRITICAL_SECTION::LeaveCriticalSection more times than you call the CRITICAL_SECTION::EnterCriticalSection function.

To avoid any ambiguities, especially when dealing with edge-cases with possible C++ exceptions in your code, and provided that you have access to newer C++ features (as opposed to just plain C), I'd strongly recommend to use the RAII technique that would allow for automatic management of the lifetime of our critical section object:

C++[Copy]
struct ENTER_CRITICAL_SECTION
{
	ENTER_CRITICAL_SECTION(CRITICAL_SECTION& cs)
	: _cs(cs)
	{
		_cs.EnterCriticalSection();
	}
	
	~ENTER_CRITICAL_SECTION()
	{
		_cs.LeaveCriticalSection();
	}
	
private:
	///Copy constructor and assignments are NOT available!
	ENTER_CRITICAL_SECTION(const ENTER_CRITICAL_SECTION& s) = delete;
	ENTER_CRITICAL_SECTION& operator = (const ENTER_CRITICAL_SECTION& s) = delete;
	
private:
	CRITICAL_SECTION& _cs;
};

Then you can easily use your critical section RAII class to control some critical area in your multithreaded app:

C++[Copy]
int updateAndGetPrevious(int add)
{
	int previous;

	//Lock critical section
	ENTER_CRITICAL_SECTION lock(_cs);

	previous = _g_v;
	_g_v += add;

	if(_g_v > 100)
	{
		_g_v = 100;
	}

	return previous;
}

private:

CRITICAL_SECTION _cs;		//Critical section for accessing data

int _g_v = 0;
It is important to note that you would not use a critical section to lock some parts of your code that require extensive computations, as that would defeat the purpose of using it in the first place.

You can get a complete source code for my critical section classes in my GitHub repo.

Reader/Writer Locks

One obvious flaw with using a critical section is that it does not differentiate between shared and exclusive access to the lock.

If your code logic requires frequent reading from a shared object, and rare writing into it (from the same process), you would get a much better performance by using a reader/writer lock.

In this context, a reader lock obtains a shared access to a resource, with multiple readers being allowed at the same time. While a writer lock obtains an exclusive access to a resource, where only a single writer is allowed at a time. In the latter case, a writer lock will work similarly to a critical section, or a mutex.

To implement a reader/writer lock for macOS I will use the pthread_rwlock_*lock functions.

As before I will put it into a class for better usability, and add some helper methods to it:

C++[Copy]
#include <pthread.h>
#include <assert.h>

struct RDR_WRTR
{
	RDR_WRTR()
	{
	}
	
	///Acquire a shared lock
	///INFO: This function does not return until the lock is available.
	///      This function DOES NOT support reentrancy, or calling it 
	///      repeatedly from the same thread!
	void EnterReaderLock()
	{
		if(pthread_rwlock_rdlock(&_lock) != 0)
        {
            //Failed to enter - abort!
			//Most certainly you have an unsupported reentrancy in your logic!
            assert(false);
			abort();
        }
	}
	
	///Leave a shared lock
	///INFO: This function must be called once after the EnterReaderLock function.
	void LeaveReaderLock()
	{
		if(pthread_rwlock_unlock(&_lock) != 0)
		{
            //Failed to leave - abort!
			//There's either a failure in logic in the code that calls this function, or
			//there's some memory corruption...
            assert(false);
			abort();
		}
	}
	
	///Debugging function - it checks if a reader lock was acquired, but it doesn't block!
	///RETURN:
	///     - Yes if lock was not available for reading (NOTE that it may be now!)
	///     - No if lock was available for reading (NOTE that it may not be anymore!)
	///     - Error if failed to determine, check errno for OS error code
	YesNoError WasReaderLocked()
	{
		YesNoError result = Error;

		//Try to acquire it for reading, but do not block!
		int nErr = pthread_rwlock_tryrdlock(&_lock);
		if(nErr == 0)
		{
			//Acquired it, need to release it
			nErr = pthread_rwlock_unlock(&_lock);
			if(nErr == 0)
			{
				result = No;
			}
			else
			{
				//Logical problem
				errno = nErr;
			}
		}
		else if(nErr == EBUSY)
		{
			//Lock was acquired
			result = Yes;
		}
		else
		{
			//Logical problem
			errno = nErr;
		}

		return result;
	}
	
	
	///Acquire an exclusive lock
	///INFO: This function does not return until the lock is available.
	///      This function DOES NOT support reentrancy, or calling it 
	///      repeatedly from the same thread!
	void EnterWriterLock()
	{
		if(pthread_rwlock_wrlock(&_lock) != 0)
        {
            //Failed to enter - abort!
			//Most certainly you have an unsupported reentrancy in your logic!
            assert(false);
			abort();
        }
	}
	
	///Leave an exclusive lock
	///INFO: This function must be called once after the EnterWriterLock function.
	void LeaveWriterLock()
	{
		if(pthread_rwlock_unlock(&_lock) != 0)
		{
            //Failed to leave - abort!
			//There's either a failure in logic in the code that calls this function, or
			//there's some memory corruption...
            assert(false);
			abort();
		}
	}

	
	///Debugging function - it checks if a writer lock was acquired, but it doesn't block!
	///RETURN:
	///     - Yes if lock was not available for writing (NOTE that it may be now!)
	///     - No if lock was available for writing (NOTE that it may not be anymore!)
	///     - Error if failed to determine, check errno for OS error code
	YesNoError WasWriterLocked()
	{
		YesNoError result = Error;

		//Try to acquire it for writing, but do not block
		int nErr = pthread_rwlock_trywrlock(&_lock);
		if(nErr == 0)
		{
			//Acquired it, need to release it
			nErr = pthread_rwlock_unlock(&_lock);
			if(nErr == 0)
			{
				result = No;
			}
			else
			{
				//Logical problem
				errno = nErr;
			}
		}
		else if(nErr == EBUSY)
		{
			//Lock was acquired
			result = Yes;
		}
		else
		{
			//Logical problem
			errno = nErr;
		}

		return result;
	}
	
private:
	///Copy constructor and assignments are NOT available!
	RDR_WRTR(const RDR_WRTR& s) = delete;
	RDR_WRTR& operator = (const RDR_WRTR& s) = delete;
	
	
private:
	pthread_rwlock_t _lock = PTHREAD_RWLOCK_INITIALIZER;
};	
As before with our critical section class, the reader/writer lock cannot fail by definition. Or, if the lock is not available, an attempt to enter it should wait indefinitely.

But, unlike our implementation of the critical section, the reader/writer lock functions provided by the OS do not support reentrancy, or calling the Enter*Lock functions more than once from the same thread without calling the Leave*Lock function. Of course, we can add that support but it will require an introduction of an overhead that otherwise may not be necessary.

To prevent the need to keep track of reentrancy, and to ensure that the Leave*Lock function is always called after the Enter*Lock function, I'll use the RAII technique.

For the reader, or shared lock:

C++[Copy]
struct READER_LOCK
{
	READER_LOCK(RDR_WRTR& rwl)
		: _rwl(rwl)
	{
		rwl.EnterReaderLock();
	}

	~READER_LOCK()
	{
		_rwl.LeaveReaderLock();
	}

	
private:
	///Copy constructor and assignments are NOT available!
	READER_LOCK(const READER_LOCK& s) = delete;
	READER_LOCK& operator = (const READER_LOCK& s) = delete;
	
private:
	RDR_WRTR& _rwl;
};

And similarly for the writer, or exclusive lock:

C++[Copy]
struct WRITER_LOCK
{
	WRITER_LOCK(RDR_WRTR& rwl)
		: _rwl(rwl)
	{
		rwl.EnterWriterLock();
	}

	~WRITER_LOCK()
	{
		_rwl.LeaveWriterLock();
	}

private:
	///Copy constructor and assignments are NOT available!
	WRITER_LOCK(const WRITER_LOCK& s) = delete;
	WRITER_LOCK& operator = (const WRITER_LOCK& s) = delete;
	
private:
	RDR_WRTR& _rwl;
};

One thing to note about the RDR_WRTR class, are its WasReaderLocked and WasWriterLocked functions. They are provided purely for debugging purposes to check the lock state at some specific time. Unlike the actual lock acquiring and releasing functions, the Was*Locked debugging functions may return a triple condition and they never abort the process in case of a failure:

C[Copy]
enum YesNoError
{
	Yes = 1,
	No = 0,
	Error = -1
}

The Yes and No results are returned to denote if the lock was acquired or not during the time of the call. While the Error result is returned in case of an error condition in some internal state of the lock. In that case, that function call will set the internal errno global state with the OS error code.

Then the use of the reader/writer lock for some critical area becomes quite trivial:

C++[Copy]
int getValue(void)
{
	//Acquire shared lock
	READER_LOCK rdl(_lock);

	return _g_v;
}

void updateValue(int add)
{
	//Acquire exclusive lock
	WRITER_LOCK wtl(_lock);

	_g_v += add;

	if(_g_v > 100)
	{
		_g_v = 100;
	}
}

private:

//Declared on the global scale
RDR_WRTR _lock;

Note that in the case above the only benefit of using the reader/writer lock can be seen if the getValue function is called way more often than the updateValue function. On the other hand, if both functions are called equally, you'd be better off using my CRITICAL_SECTION class instead.

You can get a complete source code for my reader/writer lock classes in my GitHub repo.

Conditional Reader/Writer Locks

Just to expand on my RDR_WRTR class, we can introduce a condition to its constructor upon which to acquire the lock.

For instance, we can re-write the writer lock as such:

C++[Copy]
struct WRITER_LOCK_COND
{
	WRITER_LOCK_COND(RDR_WRTR* p_rwl)
		: _p_rwl(p_rwl)
	{
		if(_p_rwl)
		{
			_p_rwl->EnterWriterLock();
		}
	}

	~WRITER_LOCK_COND()
	{
		if(_p_rwl)
		{
			_p_rwl->LeaveWriterLock();
		}
	}

private:
	///Copy constructor and assignments are NOT available!
	WRITER_LOCK_COND(const WRITER_LOCK_COND& s) = delete;
	WRITER_LOCK_COND& operator = (const WRITER_LOCK_COND& s) = delete;
	
private:
	RDR_WRTR* _p_rwl;
};

Then we can use this class to obtain a conditional writer lock, if such is required by your app's logic.

Synchronized Access Class

Quite often you may need to synchronize access to a small data class. Of course, you can use some of the synchronization principals that I showed above. But what could be much more convenient is to make a template class that will do it all for you:

C++[Copy]
template <typename T>
struct SYNCHED_DATA
{
	SYNCHED_DATA(T v)
		: _var(v)
	{
	}

	///Read the value into what is pointed by 'pV'
	void get(T* pV)
	{
		if(pV)
		{
			READER_LOCK rl(_lock);
			*pV = _var;
		}
	}

	///Set the value to what is pointed by 'pV'
	void set(T* pV)
	{
		if(pV)
		{
			WRITER_LOCK rl(_lock);
			_var = *pV;
		}
	}

	///Set the value to what is pointed by 'pV' and return its previous value
	T getAndSet(T* pV)
	{
		T prevVar;
	
		if(true)
		{
			WRITER_LOCK rl(_lock);
			prevVar = _var;

			if(pV)
			{
				_var = *pV;
			}
		}

		return prevVar;
	}

	///Call the 'pfn' callback from within the writer lock, and pass it 'pParam1' and 'pParam2'
	///RETURN: The final value stored in this class
	T callFunc_ToSet(void (*pfn)(T*, const void*, const void*),
						const void* pParam1 = nullptr,
						const void* pParam2 = nullptr)
	{
		WRITER_LOCK rl(_lock);

		pfn(&_var, pParam1, pParam2);

		return _var;
	}


private:
	///Copy constructor and assignments are NOT available!
	SYNCHED_DATA(const SYNCHED_DATA& s) = delete;
	SYNCHED_DATA& operator = (const SYNCHED_DATA& s) = delete;

	T _var;
	RDR_WRTR _lock;
};

The SYNCHED_DATA class encapsulates some handy helper functions to deal with synchronized access. For instance, if we have a class that is being accessed from multiple threads:

C++[Copy]
struct TENANT_INFO
{
	int floor;
	std::string name;
};

We can use our synchronization template class to ensure safe access to our TENANT_INFO. For that, let's first use it to declare it somewhere on a global scale and initialize it:

C++[Copy]
SYNCHED_DATA<TENANT_INFO> g_Tenant({});
Note that if you're declaring the g_Tenant as some other class member:
C++[Copy]
class Globals
{
	//...

	SYNCHED_DATA<TENANT_INFO> g_Tenant;
}
You will need to initialize it in its constructor:
C++[Copy]
Globals::Globals()
    : g_Tenant({})
{
}

Then, you can do some basic synchronized operations with it:

C++[Copy]
//To read from it
TENANT_INFO tenant;
g_Tenant.get(&tenant);

printf("Tenant floor: %d, name=\"%s\"\n", tenant.floor, tenant.name.c_str());

//Or, to set it
tenant.name = "John Doe";
g_Tenant.set(&tenant);

Additionally, for any other complex operation, you can use its callback function:

C++[Copy]
void vacateTenant(int aboveFloor)
{
	g_Tenant.callFunc_ToSet(_callback, &aboveFloor, nullptr);
}

private:

///This function is invoked from within the writer lock!
static void _callback(TENANT_INFO* pTenant, const void* pParam1, const void* pParam2)
{
	int* pFloor = (int*)pParam1;
	assert(pFloor);

	if(pTenant->floor > *pFloor)
	{
		pTenant->name.clear();
	}
}

Obviously, there are many other options that the SYNCHED_DATA class can be expanded to.

You can get a complete source code for my data synchronization classes in my GitHub repo.

How To Get The List of Current Interactive Login Sessions

Switching away from synchronization, let's review how we can obtain a list of interactive users that are currently logged in to a Mac.

I guess I need to explain what interactive login sessions are. In most cases, on a plain consumer Mac, there's only one login session - that is the user that typed their password when they booted up the macOS. Since those laptops rarely get restarted, or used more than by one person, such condition remains for the lifetime of a Mac.

Note though, that macOS doesn't limit its use to just one user. Anyone can click on the Apple logo on the top-left of the screen, and select "Lock screen" and log in under a different user name, while still keeping the first user logged in.

Such condition will create two (or more) interactive login users for that Mac. They both can theoretically use that computer without closing the apps that another user had opened.

In technical terms, each time a user types their password to log in to a Mac, they create an interactive login session. Why is it interactive? Well, because a person is interacting with a computer. On the other hand, there are some login sessions that are not interactive. Most of those are initiated by the system software running in the background.

A launch daemon running on a Mac may need to obtain a list of interactive login sessions for the purpose of communicating with its launch agent counterparts that are running in all login sessions where a user was, or is present.

To do so you can use the following C++ function, but first let's declare a class that will be used to contain information for each interactive login session that we find:

C++[Copy]
#include <string>
#include <vector>
#include <assert.h>

#include <pwd.h>
#include <utmpx.h>


struct LOGIN_SESSION_INFO
{
	std::string strUserName;    //User name
	uid_t nUsrID = -1;          //User ID (or -1 if error)
	gid_t nGrpID = -1;          //User's group ID (or -1 if error)
};

#define SIZEOF(f) (sizeof(f) / sizeof(f[0]))	//Helper preprocessor definition to get the number of elements in C array
Then the function itself to collect the info:
C++[Copy]
///Collect currently logged in user sessions
///'arrSessions' = receives info for all currently logged in user sessions
///RETURN:
///     - true if no errors
///     - false if some errors took place (note that 'arrSessions' may still contain some user sessions)
bool GetCurrentLoginSessions(std::vector<LOGIN_SESSION_INFO>& arrSessions)
{
	bool bRes = true;
	
	arrSessions.clear();
	
	LOGIN_SESSION_INFO lsi;
	
	//Reset the database
	setutxent();
	
	for(intptr_t t = 0;; t++)
	{
		//Make sure that we don't create an infinite loop here...
		//INFO: We need to do this because utmpx functions are kinda stupid.
		//      If they fail, they don't return a meaningful error code.
		if(t > 10000)
		{
			//Overflowed - something went really wrong!
			//We can't have over 10,000 user sessions...
			bRes = false;
			assert(false);
			
			break;
		}
		
		struct utmpx* pUtx = getutxent();
		if(!pUtx)
		{
			//Assume, no more ...
			break;
		}

		//Do some heuristic to see if we need this session
		if(pUtx->ut_type == USER_PROCESS &&
			pUtx->ut_id[0] == '/' &&
			pUtx->ut_id[1] == 0)
		{
			//We must have a user name
			if(pUtx->ut_user[0])
			{
				//Use this session
				pUtx->ut_user[SIZEOF(pUtx->ut_user) - 1] = 0;       //Safety null

				//This will be the user name
				lsi.strUserName = pUtx->ut_user;
				
				//Get other user info
				struct passwd *pw = getpwnam(pUtx->ut_user);
				if(pw)
				{
					lsi.nUsrID = pw->pw_uid;
					lsi.nGrpID = pw->pw_gid;
				}
				else
				{
					//Failed to get
					bRes = false;
					
					//Reset (but don't user 0's, as those are valid IDs!)
					lsi.nUsrID = -1;
					lsi.nGrpID = -1;
				}

				//Add it to our list to return back
				arrSessions.push_back(lsi);
			}
			else
			{
				//Empty user name - technically should not happen!
				bRes = false;
			}
		}
	}
	
	//Close the database
	endutxent();
	
	return bRes;
}
I would assume that my GetCurrentLoginSessions function that I showed above is not thread-safe due to its use of the utmpx functions. Thus, you may want to wrap it in one of the synchronization locks that I described above.

Note that in the code above I used some C++ constructs for the ease of dealing with dynamic memory allocations. But it does not preclude you from using the same function with just plain C. In that case though you will have to manage the memory allocations manually. I didn't write it in C to keep the code concise, as memory allocations are not the point of this blog post.

You can get a complete source code for my login user session enumeration in my GitHub repo.

How To Get Interactive Login Session That This Process Runs In

And while we're on the subject of interactive login sessions, answer this. How does a (GUI) process get a user name of the interactive login session that it runs in?

People familiar with the Linux OS will quickly dispute my windiness by dedicating another chapter to this simple task. They'd tell me to call getlogin or getlogin_r to obtain a user name.

But that will be wrong. (At least for macOS.)

On the surface the answer seems simple. But knowing the complexities of the interactive login sessions (that I briefly touched earlier) you may notice the following scenario:

  • A (GUI) process may be started "normally" by a user double-clicking it in the Finder. In that case, the getlogin_r will return the correct interactive user name that the GUI app is running under.
  • But a (GUI) process may be also launched with the use of the AuthorizationExecuteWithPrivileges function, that will run it in the same interactive user session and elevate it to root. In that case, the getlogin_r will also return the "root" user. But that will be incorrect, since the login session that the elevated process is running in is not root.
I'm saying GUI process meaning that it must be a process with a window. Technically, the same applies to a console process, but since it most certainly will run in a Terminal window, we can also assume it to have some GUI output.

To address that, we will need the SCDynamicStoreCopyConsoleUser function.

Here's a code sample in Swift:

Swift[Copy]
///Get current interactive login user info
///RETURN: (userName, userID, groupID)
///         = 'userName' for user name, or "" if error
///         = 'userID' for user ID, or UInt32.min if error
///         = 'groupID' for group ID, or UInt32.min if error
func GetCurrentInteractiveLoginUser() -> (String, uid_t, gid_t)
{
	var uid: uid_t = UInt32.min
	var gid: gid_t = UInt32.min
	var strUsrName : String = ""

	let resName = SCDynamicStoreCopyConsoleUser(nil, &uid, &gid)

	if(resName != nil)
	{
		strUsrName = resName! as String
	}    

	return (strUsrName, uid, gid)
}

The GetCurrentInteractiveLoginUser Swift function above returns a tuple, that can be used as such:

Swift[Copy]
let userInfo = GetCurrentInteractiveLoginUser()
print("Login User Name: \(userInfo.0)")

And here's the same thing in C++:

C++[Copy]
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>

bool GetCurrentInteractiveLoginUser(std::string* pstrOutUserName = nullptr,
									uid_t* pOutUserID = nullptr,
									gid_t* pOutGroupID = nullptr)
{
	bool bResult = false;
	
	std::string strUsrName;
	
	uid_t uid = -1;
	gid_t gid = -1;
	
	CFStringRef refUsrName = SCDynamicStoreCopyConsoleUser(NULL, &uid, &gid);
	if(refUsrName)
	{
		if(GetString_From_CFStringRef(refUsrName, strUsrName))
		{
			bResult = true;
		}
		else
		{
			//Failed to convert
			assert(false);
		}
		
		CFRelease(refUsrName);
	}
	
	if(pstrOutUserName)
		*pstrOutUserName = strUsrName;
	
	if(pOutUserID)
		*pOutUserID = uid;
	
	if(pOutGroupID)
		*pOutGroupID = gid;
	
	return bResult;
}	

To compile the function above you will need to add the following frameworks to your project:

  • CoreFoundation.framework
  • SystemConfiguration.framework

Additionally, I used my GetString_From_CFStringRef function that converts Apple's CFStringRef into a C++ string. I'll show it next.

How To Convert CFStringRef into std::string

Apple's CoreFoundation framework uses CFStringRef extensively to work with strings. Thus one would think that conversion from it to a more familiar std::string would be a breeze. Well, I'm not sure if you would call the following function "a breeze", but here it is:

C++[Copy]
#include <CoreFoundation/CoreFoundation.h>

///Convert 'ref' into std::string
///'strOut' = receives converted string, or an empty string if conversion fails
///RETURN:
///     = true if converted successfully
bool GetString_From_CFStringRef(CFStringRef ref, std::string& strOut)
{
	bool bRes = false;
	
	if(ref)
	{
		const char* pStr = CFStringGetCStringPtr(ref, kCFStringEncodingUTF8);
		if(pStr)
		{
			//Simple example
			strOut = pStr;
			
			bRes = true;
		}
		else
		{
			//Need to do more work
			CFIndex nchSize = CFStringGetLength(ref);     //This is the size in characters
			if(nchSize > 0)
			{
				CFIndex ncbSz = CFStringGetMaximumSizeForEncoding(nchSize,
				                                                  kCFStringEncodingUTF8);
				if(ncbSz != kCFNotFound)
				{
					UInt8* pBuff = new (std::nothrow) UInt8[ncbSz + 1];
					if(pBuff)
					{
						if(CFStringGetCString(ref,
						                      (char*)pBuff,
						                      ncbSz + 1,
						                      kCFStringEncodingUTF8))
						{
							//Note that 'ncbSz' will most certainly include trailing 0's,
							//thus we cannot assume that that is the length of our string!
							
							//Let's place a safety null there first
							pBuff[ncbSz] = 0;
							
							strOut = (const char*)pBuff;
							
							bRes = true;
						}
						else
							assert(false);
						
						//Free mem
						delete[] pBuff;
						pBuff = nullptr;
					}
					else
						assert(false);
				}
				else
					assert(false);
			}
		}
	}
	
	if(!bRes)
	{
		//Clear resulting string on error
		strOut.clear();
	}
	
	return bRes;
}
I should give Apple some slack with their CFStringRef. It does not support a built-in conversion to the STL's std::string probably because CFStringRef predates std::string by several years.

You can get a complete source code for my CFString conversion function in my GitHub repo.

How To Check If Process Is Running As Root

And since we were talking about a root user, how can you tell that your app (or process) is running as root?

Just use geteuid for that:

C[Copy]
///RETURN: - true if this process is running as root
bool IsRunningAsRoot(void)
{
	return geteuid() == 0;
}
Note that in this case it will be advisable to call geteuid to get the effective user ID instead of calling the plain getuid.

Conclusion

I originally wanted to add more tidbits to this post. And I see already that it has turned out quite large. So I will leave the rest for the part 2...

Related Articles