pattern.c   [plain text]


/*
 * Copyright (c) 2003, 2004, 2006-2008, 2011, 2012, 2015 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * Modification History
 *
 * April 23, 2003		Allan Nathanson <ajn@apple.com>
 * - initial revision
 */


#include "configd.h"
#include "pattern.h"


/*
 * Notes:
 *
 * - pattern matching information is maintained in a dictionary (patternData).
 * - the dictionary "key" is the regular expression being matched
 * - the dictionary "value" is a CFArray with the following contents
 *     [0]   = CFData consisting of the pre-compiled regular expression
 *     [1]   = CFArray[CFNumber] consisting of the sessions watching this pattern
 *     [2-n] = dynamic store keys which match this pattern
 */


typedef struct {
	CFMutableArrayRef	pInfo;
	CFDataRef		pRegex;
} addContext, *addContextRef;


static __inline__ void
my_CFDictionaryApplyFunction(CFDictionaryRef			theDict,
			     CFDictionaryApplierFunction	applier,
			     void				*context)
{
	CFAllocatorRef	myAllocator;
	CFDictionaryRef	myDict;

	myAllocator = CFGetAllocator(theDict);
	myDict      = CFDictionaryCreateCopy(myAllocator, theDict);
	CFDictionaryApplyFunction(myDict, applier, context);
	CFRelease(myDict);
	return;
}


static Boolean
keyMatchesPattern(CFStringRef key, CFDataRef pRegex)
{
	CFIndex			len;
	Boolean			match		= FALSE;
	regex_t			*preg;
	int			reError;
	char			str_q[256];
	char *			str		= str_q;

	/* convert store key to C string */
	len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(key), kCFStringEncodingASCII) + 1;
	if (len > (CFIndex)sizeof(str_q))
		str = CFAllocatorAllocate(NULL, len, 0);
	if (_SC_cfstring_to_cstring(key, str, len, kCFStringEncodingASCII) == NULL) {
		SC_log(LOG_INFO, "could not convert store key to C string");
		goto done;
	}

	/* ALIGN: CF aligns to >8 byte boundries */
	preg = (regex_t *)(void *)CFDataGetBytePtr(pRegex);

	/* compare key to regular expression pattern */
	reError = regexec(preg, str, 0, NULL, 0);
	switch (reError) {
		case 0 :
			match = TRUE;
			break;
		case REG_NOMATCH :
			/* no match */
			break;
		default : {
			char	reErrBuf[256];

			(void)regerror(reError, preg, reErrBuf, sizeof(reErrBuf));
			SC_log(LOG_INFO, "regexec() failed: %s", reErrBuf);
			break;
		}
	}

    done :

	if (str != str_q) CFAllocatorDeallocate(NULL, str);
	return match;
}


static void
identifyKeyForPattern(const void *key, void *val, void *context)
{
	CFStringRef		storeKey	= (CFStringRef)key;
	CFDictionaryRef		storeValue	= (CFDictionaryRef)val;
	CFMutableArrayRef	pInfo		= ((addContextRef)context)->pInfo;
	CFDataRef		pRegex		= ((addContextRef)context)->pRegex;

	if (!CFDictionaryContainsKey(storeValue, kSCDData)) {
		/* if no data (yet) */
		return;
	}

	if (keyMatchesPattern(storeKey, pRegex)) {
		/* if we've got a match */
		CFArrayAppendValue(pInfo, storeKey);
	}

	return;
}


static Boolean
patternCompile(CFStringRef pattern, CFDataRef pRegex, CFStringRef *error)
{
	Boolean		append		= FALSE;
	Boolean		insert		= FALSE;
	CFIndex		len		= 0;
	CFIndex		len_c;
	Boolean		ok;
	char		str_q[256];
	char *		str		= str_q;

	if (CFStringGetLength(pattern) == 0) {
		SC_log(LOG_NOTICE, "empty regex pattern");
	}

	if (!CFStringHasPrefix(pattern, CFSTR("^"))) {
		insert = TRUE;
	}

	if (!CFStringHasSuffix(pattern, CFSTR("$")) ||
	     CFStringHasSuffix(pattern, CFSTR("\\$"))) {
		append = TRUE;
	}

	/* if regex pattern is not bounded at both ends */
	if (insert || append) {
	      pattern = CFStringCreateWithFormat(NULL,
						 NULL,
						 CFSTR("%s%@%s"),
						 insert ? "^" : "",
						 pattern,
						 append ? "$" : "");
	}

	len_c = CFStringGetBytes(pattern,
				 CFRangeMake(0, CFStringGetLength(pattern)),
				 kCFStringEncodingASCII,
				 0,
				 FALSE,
				 NULL,
				 0,
				 &len);
	if (len_c <= 0) {
		SC_log(LOG_NOTICE, "could not get buffer length for \"%@\"", pattern);
		len = sizeof(str_q) - 1;
	}
	if (++len > (CFIndex)sizeof(str_q)) {
		str = CFAllocatorAllocate(NULL, len, 0);
	}
	ok = (_SC_cfstring_to_cstring(pattern, str, len, kCFStringEncodingASCII) != NULL);
	if (insert || append) {
		CFRelease(pattern);
	}
	if (ok) {
		regex_t	*preg;
		int	reError;

		/* ALIGN: CF aligns to >8 byte boundries */
		preg = (regex_t *)(void *)CFDataGetBytePtr(pRegex);

		reError = regcomp(preg, str, REG_EXTENDED);
		if (reError != 0) {
			char	reErrBuf[256];

			(void)regerror(reError, preg, reErrBuf, sizeof(reErrBuf));
			*error = CFStringCreateWithCString(NULL, reErrBuf, kCFStringEncodingASCII);
#ifdef	DEBUG
			SC_log(LOG_DEBUG, "regcomp(%s) failed: %s", str, reErrBuf);
#endif	/* DEBUG */
			ok = FALSE;
		}
	} else {
		*error = CFRetain(CFSTR("could not convert pattern to regex string"));
#ifdef	DEBUG
		SC_log(LOG_DEBUG, "%@", *error);
#endif	/* DEBUG */
	}

	if (str != str_q)	CFAllocatorDeallocate(NULL, str);
	return ok;
}


static void
patternRelease(CFDataRef pRegex)
{
	regex_t		*preg;

	/* ALIGN: CF aligns to >8 byte boundries */
	preg = (regex_t *)(void *)CFDataGetBytePtr(pRegex);
	regfree(preg);

	return;
}


static CF_RETURNS_RETAINED CFMutableArrayRef
patternCopy(CFStringRef	pattern)
{
	CFArrayRef	pInfo;

	pInfo = CFDictionaryGetValue(patternData, pattern);
	return (pInfo != NULL) ? CFArrayCreateMutableCopy(NULL, 0, pInfo) : NULL;
}


static CF_RETURNS_RETAINED CFMutableArrayRef
patternNew(CFStringRef pattern)
{
	addContext		context;
	CFStringRef		err	= NULL;
	CFMutableArrayRef	pInfo;
	CFMutableDataRef	pRegex;
	CFArrayRef		pSessions;

	/* create the pattern info */
	pInfo = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

	/* compile the regular expression from the pattern string. */
	pRegex = CFDataCreateMutable(NULL, sizeof(regex_t));
	CFDataSetLength(pRegex, sizeof(regex_t));
	if (!patternCompile(pattern, pRegex, &err)) {
		CFRelease(err);
		CFRelease(pRegex);
		CFRelease(pInfo);
		return NULL;
	}

	/* add the compiled regular expression to the pattern info */
	CFArrayAppendValue(pInfo, pRegex);

	/* add the initial (empty) list of sessions watching this pattern */
	pSessions = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
	CFArrayAppendValue(pInfo, pSessions);
	CFRelease(pSessions);

	/* identify/add all existing keys that match the specified pattern */
	context.pInfo  = pInfo;
	context.pRegex = pRegex;
	my_CFDictionaryApplyFunction(storeData,
				     (CFDictionaryApplierFunction)identifyKeyForPattern,
				     &context);

	CFRelease(pRegex);
	return pInfo;
}


__private_extern__
CFArrayRef
patternCopyMatches(CFStringRef pattern)
{
	Boolean			isNew	= FALSE;
	CFArrayRef		keys;
	CFMutableArrayRef	pInfo;

	/* find (or create new instance of) this pattern */
	pInfo = patternCopy(pattern);
	if (pInfo == NULL) {
		/* if new pattern */
		pInfo = patternNew(pattern);
		if (pInfo == NULL) {
			return NULL;
		}

		isNew = TRUE;
	}

	if (isNew) {
		CFDataRef	pRegex;

		pRegex = CFArrayGetValueAtIndex(pInfo, 0);
		patternRelease(pRegex);
	}

	CFArrayReplaceValues(pInfo, CFRangeMake(0, 2), NULL, 0);
	keys = CFArrayCreateCopy(NULL, pInfo);
	CFRelease(pInfo);

	return keys;
}


__private_extern__
Boolean
patternKeyMatches(CFStringRef pattern, CFStringRef key)
{
	Boolean			isNew	= FALSE;
	Boolean			match	= FALSE;
	CFMutableArrayRef	pInfo;
	CFDataRef		pRegex;

	/* find (or create new instance of) this pattern */
	pInfo = patternCopy(pattern);
	if (pInfo != NULL) {
		CFIndex		n;

		/* if existing pattern, check if known key */
		n = CFArrayGetCount(pInfo);
		match = (n > 2) &&
			CFArrayContainsValue(pInfo, CFRangeMake(2, n - 2), key);
		if (match) {
			goto done;
		}
	} else {
		/* if new pattern */
		pInfo = patternNew(pattern);
		if (pInfo == NULL) {
			return FALSE;
		}

		isNew = TRUE;
	}

	pRegex = CFArrayGetValueAtIndex(pInfo, 0);
	match = keyMatchesPattern(key, pRegex);

	if (isNew) {
		patternRelease(pRegex);
	}

    done :

	CFRelease(pInfo);

	return match;
}


__private_extern__
Boolean
patternAddSession(CFStringRef pattern, CFNumberRef sessionNum)
{
	CFIndex                 i;
	CFIndex                 n;
	CFMutableArrayRef       pInfo;
	CFMutableArrayRef	pSessions;

	/* find (or create new instance of) this pattern */
	pInfo = patternCopy(pattern);
	if (pInfo == NULL) {
		/* if new pattern */
		pInfo = patternNew(pattern);
		if (pInfo == NULL) {
			return FALSE;
		}
	}

	/* add this session as a pattern watcher */
	pSessions = (CFMutableArrayRef)CFArrayGetValueAtIndex(pInfo, 1);
	pSessions = CFArrayCreateMutableCopy(NULL, 0, pSessions);
	CFArrayAppendValue(pSessions, sessionNum);
	CFArraySetValueAtIndex(pInfo, 1, pSessions);
	CFRelease(pSessions);

	/* update pattern watcher info */
	CFDictionarySetValue(patternData, pattern, pInfo);

	/* add this session as a watcher of any existing keys */
	n = CFArrayGetCount(pInfo);
	for (i = 2; i < n; i++) {
		CFStringRef     matchingKey;

		matchingKey = CFArrayGetValueAtIndex(pInfo, i);
		_addWatcher(sessionNum, matchingKey);
	}

	CFRelease(pInfo);
	return TRUE;
}


__private_extern__
void
patternRemoveSession(CFStringRef pattern, CFNumberRef sessionNum)
{
	CFIndex                 i;
	CFIndex                 n;
	CFMutableArrayRef       pInfo;
	CFDataRef		pRegex;
	CFMutableArrayRef	pSessions;

	/* find instance of this pattern */
	pInfo = patternCopy(pattern);
	assert(pInfo != NULL);

	/* remove this session as a watcher from all matching keys */
	n = CFArrayGetCount(pInfo);
	for (i = 2; i < n; i++) {
		CFStringRef     matchingKey;

		matchingKey = CFArrayGetValueAtIndex(pInfo, i);
		_removeWatcher(sessionNum, matchingKey);
	}

	/* remove session from watchers */
	pSessions = (CFMutableArrayRef)CFArrayGetValueAtIndex(pInfo, 1);
	n = CFArrayGetCount(pSessions);
	if (n > 1) {
		/* if other sessions are watching this pattern */

		pSessions = CFArrayCreateMutableCopy(NULL, 0, pSessions);
		i = CFArrayGetFirstIndexOfValue(pSessions, CFRangeMake(0, n), sessionNum);
		CFArrayRemoveValueAtIndex(pSessions, i);
		CFArraySetValueAtIndex(pInfo, 1, pSessions);
		CFRelease(pSessions);

		CFDictionarySetValue(patternData, pattern, pInfo);
	} else {
		/* if no other sessions are watching this pattern */

		pRegex = CFArrayGetValueAtIndex(pInfo, 0);
		patternRelease(pRegex);
		CFDictionaryRemoveValue(patternData, pattern);
	}

	CFRelease(pInfo);
	return;
}


static void
addKeyForPattern(const void *key, void *val, void *context)
{
	CFStringRef		pattern		= (CFStringRef)key;
	CFArrayRef		pInfo		= (CFArrayRef)val;
	CFStringRef		storeKey	= (CFStringRef)context;

	CFIndex			len;
	regex_t			*preg;
	int			reError;
	char			str_q[256];
	char *			str		= str_q;

	/* convert store key to C string */
	len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(storeKey), kCFStringEncodingASCII) + 1;
	if (len > (CFIndex)sizeof(str_q))
		str = CFAllocatorAllocate(NULL, len, 0);
	if (_SC_cfstring_to_cstring(storeKey, str, len, kCFStringEncodingASCII) == NULL) {
		SC_log(LOG_INFO, "could not convert store key to C string");
		goto done;
	}

	/* compare new store key to regular expression pattern */
	/* ALIGN: CF aligns to >8 byte boundries */
	preg = (regex_t *)(void *)CFDataGetBytePtr(CFArrayGetValueAtIndex(pInfo, 0));
	reError = regexec(preg, str, 0, NULL, 0);
	switch (reError) {
		case 0 : {
			/*
			 * we've got a match
			 */
			CFIndex			i;
			CFIndex			n;
			CFMutableArrayRef	pInfo_new;
			CFArrayRef		pSessions;

			/* add watchers */
			pSessions = CFArrayGetValueAtIndex(pInfo, 1);
			n = CFArrayGetCount(pSessions);
			for (i = 0; i < n; i++) {
				CFNumberRef	sessionNum	= CFArrayGetValueAtIndex(pSessions, i);

				_addWatcher(sessionNum, storeKey);
			}

			/* add key, update pattern watcher info */
			pInfo_new = CFArrayCreateMutableCopy(NULL, 0, pInfo);
			CFArrayAppendValue(pInfo_new, storeKey);
			CFDictionarySetValue(patternData, pattern, pInfo_new);
			CFRelease(pInfo_new);
			break;
		}
		case REG_NOMATCH :
			/* no match */
			break;
		default : {
			char	reErrBuf[256];

			(void)regerror(reError, preg, reErrBuf, sizeof(reErrBuf));
			SC_log(LOG_INFO, "%s", reErrBuf);
			break;
		}
	}

    done :

	if (str != str_q) CFAllocatorDeallocate(NULL, str);
	return;
}


__private_extern__
void
patternAddKey(CFStringRef key)
{
	void	*context	= (void *)key;

	my_CFDictionaryApplyFunction(patternData,
				     (CFDictionaryApplierFunction)addKeyForPattern,
				     context);

	return;
}


static void
removeKeyFromPattern(const void *key, void *val, void *context)
{
	CFStringRef		pattern		= (CFStringRef)key;
	CFArrayRef		pInfo		= (CFArrayRef)val;
	CFStringRef		storeKey	= (CFStringRef)context;

	CFIndex			i;
	CFIndex			n;
	CFMutableArrayRef	pInfo_new;
	CFArrayRef		pSessions;

	n = CFArrayGetCount(pInfo);
	if (n <= 2) {
		/* if no keys match this pattern */
		return;
	}

	i = CFArrayGetFirstIndexOfValue(pInfo, CFRangeMake(2, n-2), storeKey);
	if (i == kCFNotFound) {
		/* if this key wasn't matched by this pattern */
		return;
	}

	/* remove key from pattern info */
	pInfo_new = CFArrayCreateMutableCopy(NULL, 0, pInfo);
	CFArrayRemoveValueAtIndex(pInfo_new, i);

	/* remove watchers */
	pSessions = CFArrayGetValueAtIndex(pInfo_new, 1);
	n = CFArrayGetCount(pSessions);
	for (i = 0; i < n; i++) {
		CFNumberRef	sessionNum	= CFArrayGetValueAtIndex(pSessions, i);

		_removeWatcher(sessionNum, storeKey);
	}

	CFDictionarySetValue(patternData, pattern, pInfo_new);
	CFRelease(pInfo_new);
	return;
}


__private_extern__
void
patternRemoveKey(CFStringRef key)
{
	void	*context	= (void *)key;

	my_CFDictionaryApplyFunction(patternData,
				     (CFDictionaryApplierFunction)removeKeyFromPattern,
				     context);

	return;
}