Since the functions pthread_setcancelstate() and pthread_setcanceltype() return the old state via the argument list, you can call the functions to set the 'safe' state/type (PTHREAD_CANCEL_DISABLE and PTHREAD_CANCEL_DEFERRED, I think), and if the old state/type was not the same, call the function a second time to reinstate the old state/type. There doesn't seem to be a 'no-op' value for the new state/type.
int pthread_getcancelstate(int *oldstate)
{
int dont_care;
int rc = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, oldstate);
if (rc == 0 && *oldstate != PTHREAD_CANCEL_DISABLE)
rc = pthread_setcancelstate(*oldstate, &dont_care);
return rc;
}
int pthread_getcanceltype(int *oldtype)
{
int dont_care;
int rc = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, oldtype);
if (rc == 0 && *oldtype != PTHREAD_CANCEL_DEFERRED)
rc = pthread_setcanceltype(*oldtype, &dont_care);
return rc;
}
The other state is PTHREAD_CANCEL_ENABLE and the other type is PTHREAD_CANCEL_ASYNCHRONOUS.
The manual pages for these functions on macOS do say that it is permissible to pass a null pointer for the old state/type, so it would be possible to eliminate the dont_care variable on macOS. However, neither the POSIX specification nor the manual page on (RHEL 7.4) Linux say that a null pointer is permissible. The code above should work safely on both those platforms.
The code is also trespassing on the namespace reserved for use by the implementation — the implementation could choose to add either or both pthread_getcancelstate() or pthread_getcanceltype() (with the same or a different interface) and there'd be no option but to rename the functions shown above. Adding the _np (non-portable) suffix might not help either; the implementation could choose to use the name with the suffix.