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
- Reader/Writer Locks
- Conditional Reader/Writer Locks
- Synchronized Access Class
- How To Get The List of Current Interactive Login Sessions
- How To Get Interactive Login Session That This Process Runs In
- How To Convert CFStringRef into std::string
- How To Check If Process Is Running As Root
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'spthread_mutex_lock
would be Apple'sOSSpinLock
or its newer counterpartOSAllocatedUnfairLock
. 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:
#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:
- You have a memory corruption that messed up the internal mutex data structure.
- The logic in your app is broken. Say, if you're calling
CRITICAL_SECTION::LeaveCriticalSection
more times than you call theCRITICAL_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:
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:
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:
#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 theLeave*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:
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:
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:
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:
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:
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:
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:
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:
Note that if you're declaring the g_Tenant
as some other class member:
You will need to initialize it in its constructor:
Then, you can do some basic synchronized operations with it:
//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:
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:
#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
///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 callgetlogin
orgetlogin_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 toroot
. In that case, thegetlogin_r
will also return the "root" user. But that will be incorrect, since the login session that the elevated process is running in is notroot
.
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:
///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:
And here's the same thing in C++:
#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:
#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 theirCFStringRef
. It does not support a built-in conversion to the STL'sstd::string
probably becauseCFStringRef
predatesstd::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:
///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 callgeteuid
to get the effective user ID instead of calling the plaingetuid
.
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...