Skip to content
Snippets Groups Projects
utils.c 73.6 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * \brief A list of each thread's lock info
    
     */
    static AST_LIST_HEAD_NOLOCK_STATIC(lock_infos, thr_lock_info);
    
    /*!
     * \brief Destroy a thread's lock info
     *
     * This gets called automatically when the thread stops
     */
    static void lock_info_destroy(void *data)
    {
    	struct thr_lock_info *lock_info = data;
    
    
    	pthread_mutex_lock(&lock_infos_lock.mutex);
    	AST_LIST_REMOVE(&lock_infos, lock_info, entry);
    	pthread_mutex_unlock(&lock_infos_lock.mutex);
    
    
    
    	for (i = 0; i < lock_info->num_locks; i++) {
    
    		if (lock_info->locks[i].pending == -1) {
    			/* This just means that the last lock this thread went for was by
    			 * using trylock, and it failed.  This is fine. */
    			break;
    		}
    
    
    		ast_log(LOG_ERROR,
    			"Thread '%s' still has a lock! - '%s' (%p) from '%s' in %s:%d!\n",
    
    			lock_info->thread_name,
    			lock_info->locks[i].lock_name,
    			lock_info->locks[i].lock_addr,
    			lock_info->locks[i].func,
    			lock_info->locks[i].file,
    			lock_info->locks[i].line_num
    		);
    	}
    
    
    	pthread_mutex_destroy(&lock_info->lock);
    
    	if (lock_info->thread_name) {
    		ast_free((void *) lock_info->thread_name);
    	}
    	ast_free(lock_info);
    
    }
    
    /*!
     * \brief The thread storage key for per-thread lock info
     */
    AST_THREADSTORAGE_CUSTOM(thread_lock_info, NULL, lock_info_destroy);
    
    #endif /* ! LOW_MEMORY */
    
    
    void ast_store_lock_info(enum ast_lock_type type, const char *filename,
    	int line_num, const char *func, const char *lock_name, void *lock_addr, struct ast_bt *bt)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    	int i;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return;
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	for (i = 0; i < lock_info->num_locks; i++) {
    		if (lock_info->locks[i].lock_addr == lock_addr) {
    			lock_info->locks[i].times_locked++;
    
    #ifdef HAVE_BKTR
    			lock_info->locks[i].backtrace = bt;
    #endif
    
    			pthread_mutex_unlock(&lock_info->lock);
    			return;
    		}
    	}
    
    	if (lock_info->num_locks == AST_MAX_LOCKS) {
    		/* Can't use ast_log here, because it will cause infinite recursion */
    		fprintf(stderr, "XXX ERROR XXX A thread holds more locks than '%d'."
    			"  Increase AST_MAX_LOCKS!\n", AST_MAX_LOCKS);
    		pthread_mutex_unlock(&lock_info->lock);
    		return;
    	}
    
    	if (i && lock_info->locks[i - 1].pending == -1) {
    
    		/* The last lock on the list was one that this thread tried to lock but
    		 * failed at doing so.  It has now moved on to something else, so remove
    		 * the old lock from the list. */
    		i--;
    		lock_info->num_locks--;
    		memset(&lock_info->locks[i], 0, sizeof(lock_info->locks[0]));
    	}
    
    
    	lock_info->locks[i].file = filename;
    	lock_info->locks[i].line_num = line_num;
    	lock_info->locks[i].func = func;
    	lock_info->locks[i].lock_name = lock_name;
    	lock_info->locks[i].lock_addr = lock_addr;
    	lock_info->locks[i].times_locked = 1;
    
    	lock_info->locks[i].type = type;
    
    	lock_info->locks[i].pending = 1;
    
    #ifdef HAVE_BKTR
    	lock_info->locks[i].backtrace = bt;
    #endif
    
    	lock_info->num_locks++;
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    #endif /* ! LOW_MEMORY */
    
    void ast_mark_lock_acquired(void *lock_addr)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return;
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	if (lock_info->locks[lock_info->num_locks - 1].lock_addr == lock_addr) {
    		lock_info->locks[lock_info->num_locks - 1].pending = 0;
    	}
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    #endif /* ! LOW_MEMORY */
    
    void ast_mark_lock_failed(void *lock_addr)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return;
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	if (lock_info->locks[lock_info->num_locks - 1].lock_addr == lock_addr) {
    		lock_info->locks[lock_info->num_locks - 1].pending = -1;
    		lock_info->locks[lock_info->num_locks - 1].times_locked--;
    	}
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    #endif /* ! LOW_MEMORY */
    
    int ast_find_lock_info(void *lock_addr, char *filename, size_t filename_size, int *lineno, char *func, size_t func_size, char *mutex_name, size_t mutex_name_size)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    	int i = 0;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return -1;
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	for (i = lock_info->num_locks - 1; i >= 0; i--) {
    		if (lock_info->locks[i].lock_addr == lock_addr)
    			break;
    	}
    
    	if (i == -1) {
    		/* Lock not found :( */
    		pthread_mutex_unlock(&lock_info->lock);
    		return -1;
    	}
    
    
    	ast_copy_string(filename, lock_info->locks[i].file, filename_size);
    
    	*lineno = lock_info->locks[i].line_num;
    
    	ast_copy_string(func, lock_info->locks[i].func, func_size);
    	ast_copy_string(mutex_name, lock_info->locks[i].lock_name, mutex_name_size);
    
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    
    #else /* if defined(LOW_MEMORY) */
    	return -1;
    #endif
    
    void ast_suspend_lock_info(void *lock_addr)
    {
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    	int i = 0;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info)))) {
    		return;
    	}
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	for (i = lock_info->num_locks - 1; i >= 0; i--) {
    		if (lock_info->locks[i].lock_addr == lock_addr)
    			break;
    	}
    
    	if (i == -1) {
    		/* Lock not found :( */
    		pthread_mutex_unlock(&lock_info->lock);
    		return;
    	}
    
    	lock_info->locks[i].suspended = 1;
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    #endif /* ! LOW_MEMORY */
    
    }
    
    void ast_restore_lock_info(void *lock_addr)
    {
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    	int i = 0;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return;
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	for (i = lock_info->num_locks - 1; i >= 0; i--) {
    		if (lock_info->locks[i].lock_addr == lock_addr)
    			break;
    	}
    
    	if (i == -1) {
    		/* Lock not found :( */
    		pthread_mutex_unlock(&lock_info->lock);
    		return;
    	}
    
    	lock_info->locks[i].suspended = 0;
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    #endif /* ! LOW_MEMORY */
    
    void ast_remove_lock_info(void *lock_addr, struct ast_bt *bt)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    	int i = 0;
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return;
    
    	pthread_mutex_lock(&lock_info->lock);
    
    	for (i = lock_info->num_locks - 1; i >= 0; i--) {
    		if (lock_info->locks[i].lock_addr == lock_addr)
    			break;
    	}
    
    	if (i == -1) {
    		/* Lock not found :( */
    		pthread_mutex_unlock(&lock_info->lock);
    		return;
    	}
    
    	if (lock_info->locks[i].times_locked > 1) {
    		lock_info->locks[i].times_locked--;
    
    #ifdef HAVE_BKTR
    		lock_info->locks[i].backtrace = bt;
    #endif
    
    		pthread_mutex_unlock(&lock_info->lock);
    		return;
    	}
    
    	if (i < lock_info->num_locks - 1) {
    		/* Not the last one ... *should* be rare! */
    
    		memmove(&lock_info->locks[i], &lock_info->locks[i + 1],
    
    			(lock_info->num_locks - (i + 1)) * sizeof(lock_info->locks[0]));
    	}
    
    	lock_info->num_locks--;
    
    	pthread_mutex_unlock(&lock_info->lock);
    
    #endif /* ! LOW_MEMORY */
    
    #if !defined(LOW_MEMORY)
    
    static const char *locktype2str(enum ast_lock_type type)
    {
    	switch (type) {
    	case AST_MUTEX:
    		return "MUTEX";
    	case AST_RDLOCK:
    		return "RDLOCK";
    	case AST_WRLOCK:
    		return "WRLOCK";
    	}
    
    	return "UNKNOWN";
    }
    
    
    #ifdef HAVE_BKTR
    static void append_backtrace_information(struct ast_str **str, struct ast_bt *bt)
    {
    
    	struct ast_vector_string *symbols;
    
    
    	if (!bt) {
    		ast_str_append(str, 0, "\tNo backtrace to print\n");
    		return;
    	}
    
    
    	/* store frame count locally to avoid the memory corruption that
    	 * sometimes happens on virtualized CentOS 6.x systems */
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	num_frames = bt->num_frames;
    
    	if ((symbols = ast_bt_get_symbols(bt->addresses, num_frames))) {
    
    		for (frame_iterator = 1; frame_iterator < AST_VECTOR_SIZE(symbols); ++frame_iterator) {
    			ast_str_append(str, 0, "\t%s\n", AST_VECTOR_GET(symbols, frame_iterator));
    
    		ast_bt_free_symbols(symbols);
    
    	} else {
    		ast_str_append(str, 0, "\tCouldn't retrieve backtrace symbols\n");
    	}
    }
    #endif
    
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    static void append_lock_information(struct ast_str **str, struct thr_lock_info *lock_info, int i)
    {
    	int j;
    	ast_mutex_t *lock;
    
    	ast_str_append(str, 0, "=== ---> %sLock #%d (%s): %s %d %s %s %p (%d%s)\n",
    
    				   lock_info->locks[i].pending > 0 ? "Waiting for " :
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    				   lock_info->locks[i].pending < 0 ? "Tried and failed to get " : "", i,
    
    				   lock_info->locks[i].file,
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    				   locktype2str(lock_info->locks[i].type),
    				   lock_info->locks[i].line_num,
    				   lock_info->locks[i].func, lock_info->locks[i].lock_name,
    
    				   lock_info->locks[i].lock_addr,
    
    				   lock_info->locks[i].times_locked,
    				   lock_info->locks[i].suspended ? " - suspended" : "");
    
    #ifdef HAVE_BKTR
    	append_backtrace_information(str, lock_info->locks[i].backtrace);
    #endif
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	if (!lock_info->locks[i].pending || lock_info->locks[i].pending == -1)
    		return;
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	/* We only have further details for mutexes right now */
    	if (lock_info->locks[i].type != AST_MUTEX)
    		return;
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	lock = lock_info->locks[i].lock_addr;
    
    	ast_reentrancy_lock(lt);
    	for (j = 0; *str && j < lt->reentrancy; j++) {
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    		ast_str_append(str, 0, "=== --- ---> Locked Here: %s line %d (%s)\n",
    
    					   lt->file[j], lt->lineno[j], lt->func[j]);
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	}
    
    	ast_reentrancy_unlock(lt);
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    }
    
    #endif /* ! LOW_MEMORY */
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    
    
    /*! This function can help you find highly temporal locks; locks that happen for a
    
    Steve Murphy's avatar
     
    Steve Murphy committed
        short time, but at unexpected times, usually at times that create a deadlock,
    	Why is this thing locked right then? Who is locking it? Who am I fighting
    
        with for this lock?
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    
    	To answer such questions, just call this routine before you would normally try
    
    Josh Soref's avatar
    Josh Soref committed
    	to acquire a lock. It doesn't do anything if the lock is not acquired. If the
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	lock is taken, it will publish a line or two to the console via ast_log().
    
    	Sometimes, the lock message is pretty uninformative. For instance, you might
    
    Josh Soref's avatar
    Josh Soref committed
    	find that the lock is being acquired deep within the astobj2 code; this tells
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	you little about higher level routines that call the astobj2 routines.
    	But, using gdb, you can set a break at the ast_log below, and for that
    	breakpoint, you can set the commands:
    	  where
    	  cont
    	which will give a stack trace and continue. -- that aught to do the job!
    
    */
    
    void ast_log_show_lock(void *this_lock_addr)
    
    #if !defined(LOW_MEMORY)
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	struct ast_str *str;
    
    	if (!(str = ast_str_create(4096))) {
    		ast_log(LOG_NOTICE,"Could not create str\n");
    		return;
    	}
    
    
    	pthread_mutex_lock(&lock_infos_lock.mutex);
    	AST_LIST_TRAVERSE(&lock_infos, lock_info, entry) {
    		int i;
    		pthread_mutex_lock(&lock_info->lock);
    		for (i = 0; str && i < lock_info->num_locks; i++) {
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    			/* ONLY show info about this particular lock, if
    			   it's acquired... */
    
    			if (lock_info->locks[i].lock_addr == this_lock_addr) {
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    				append_lock_information(&str, lock_info, i);
    
    				ast_log(LOG_NOTICE, "%s", ast_str_buffer(str));
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    				break;
    
    			}
    		}
    		pthread_mutex_unlock(&lock_info->lock);
    	}
    	pthread_mutex_unlock(&lock_infos_lock.mutex);
    
    Steve Murphy's avatar
     
    Steve Murphy committed
    	ast_free(str);
    
    #endif /* ! LOW_MEMORY */
    
    struct ast_str *ast_dump_locks(void)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_lock_info *lock_info;
    
    	if (!(str = ast_str_create(4096))) {
    
    	ast_str_append(&str, 0, "\n"
    
    	               "=======================================================================\n"
    
    	               "=== %s\n"
    	               "=== Currently Held Locks\n"
    
    	               "=======================================================================\n"
    	               "===\n"
    
    	               "=== <pending> <lock#> (<file>): <lock type> <line num> <function> <lock name> <lock addr> (times locked)\n"
    
    	               "===\n", ast_get_version());
    
    	pthread_mutex_lock(&lock_infos_lock.mutex);
    	AST_LIST_TRAVERSE(&lock_infos, lock_info, entry) {
    		int i;
    
    		int header_printed = 0;
    		pthread_mutex_lock(&lock_info->lock);
    		for (i = 0; str && i < lock_info->num_locks; i++) {
    			/* Don't show suspended locks */
    			if (lock_info->locks[i].suspended) {
    				continue;
    
    				if (lock_info->lwp != -1) {
    					ast_str_append(&str, 0, "=== Thread ID: 0x%lx LWP:%d (%s)\n",
    
    						(long unsigned) lock_info->thread_id, lock_info->lwp, lock_info->thread_name);
    
    				} else {
    					ast_str_append(&str, 0, "=== Thread ID: 0x%lx (%s)\n",
    
    						(long unsigned) lock_info->thread_id, lock_info->thread_name);
    
    				header_printed = 1;
    			}
    
    			append_lock_information(&str, lock_info, i);
    		}
    		pthread_mutex_unlock(&lock_info->lock);
    		if (!str) {
    			break;
    		}
    		if (header_printed) {
    
    			ast_str_append(&str, 0, "=== -------------------------------------------------------------------\n"
    
    		}
    	}
    	pthread_mutex_unlock(&lock_infos_lock.mutex);
    
    
    	ast_str_append(&str, 0, "=======================================================================\n"
    
    #else /* if defined(LOW_MEMORY) */
    	return NULL;
    #endif
    
    #if !defined(LOW_MEMORY)
    
    static char *handle_show_locks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct ast_str *str;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "core show locks";
    		e->usage =
    			"Usage: core show locks\n"
    			"       This command is for lock debugging.  It prints out which locks\n"
    			"are owned by each active thread.\n";
    
    		ast_cli_allow_at_shutdown(e);
    
    		return NULL;
    
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	str = ast_dump_locks();
    	if (!str) {
    
    		return CLI_FAILURE;
    
    	ast_cli(a->fd, "%s", ast_str_buffer(str));
    
    	return CLI_SUCCESS;
    
    }
    
    static struct ast_cli_entry utils_cli[] = {
    
    	AST_CLI_DEFINE(handle_show_locks, "Show which locks are held by which thread"),
    
    #endif /* ! LOW_MEMORY */
    
    #endif /* DEBUG_THREADS */
    
    
    #if !defined(LOW_MEMORY)
    
    /*
     * support for 'show threads'. The start routine is wrapped by
     * dummy_start(), so that ast_register_thread() and
     * ast_unregister_thread() know the thread identifier.
     */
    struct thr_arg {
    	void *(*start_routine)(void *);
    	void *data;
    	char *name;
    };
    
    /*
     * on OS/X, pthread_cleanup_push() and pthread_cleanup_pop()
     * are odd macros which start and end a block, so they _must_ be
     * used in pairs (the latter with a '1' argument to call the
     * handler on exit.
    
     * On BSD we don't need this, but we keep it for compatibility.
    
     */
    static void *dummy_start(void *data)
    
    	struct thr_arg a = *((struct thr_arg *) data);	/* make a local copy */
    
    #ifdef DEBUG_THREADS
    	struct thr_lock_info *lock_info;
    
    	pthread_mutexattr_t mutex_attr;
    
    
    	if (!(lock_info = ast_threadstorage_get(&thread_lock_info, sizeof(*lock_info))))
    		return NULL;
    
    	lock_info->thread_id = pthread_self();
    
    	lock_info->lwp = ast_get_tid();
    
    	lock_info->thread_name = ast_strdup(a.name);
    
    
    	pthread_mutexattr_init(&mutex_attr);
    	pthread_mutexattr_settype(&mutex_attr, AST_MUTEX_KIND);
    	pthread_mutex_init(&lock_info->lock, &mutex_attr);
    	pthread_mutexattr_destroy(&mutex_attr);
    
    
    	pthread_mutex_lock(&lock_infos_lock.mutex); /* Intentionally not the wrapper */
    	AST_LIST_INSERT_TAIL(&lock_infos, lock_info, entry);
    	pthread_mutex_unlock(&lock_infos_lock.mutex); /* Intentionally not the wrapper */
    #endif /* DEBUG_THREADS */
    
    
    	/* note that even though data->name is a pointer to allocated memory,
    	   we are not freeing it here because ast_register_thread is going to
    	   keep a copy of the pointer and then ast_unregister_thread will
    	   free the memory
    	*/
    	ast_free(data);
    	ast_register_thread(a.name);
    	pthread_cleanup_push(ast_unregister_thread, (void *) pthread_self());
    
    
    	ret = a.start_routine(a.data);
    
    	pthread_cleanup_pop(1);
    
    #endif /* !LOW_MEMORY */
    
    
    int ast_background_stacksize(void)
    {
    #if !defined(LOW_MEMORY)
    	return AST_STACKSIZE;
    #else
    	return AST_STACKSIZE_LOW;
    #endif
    }
    
    
    int ast_pthread_create_stack(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *),
    			     void *data, size_t stacksize, const char *file, const char *caller,
    			     int line, const char *start_fn)
    
    #if !defined(LOW_MEMORY)
    
    	struct thr_arg *a;
    
    	if (!attr) {
    
    		attr = ast_alloca(sizeof(*attr));
    
    		pthread_attr_init(attr);
    
    #if defined(__linux__) || defined(__FreeBSD__)
    	/* On Linux and FreeBSD , pthread_attr_init() defaults to PTHREAD_EXPLICIT_SCHED,
    
    	   which is kind of useless. Change this here to
    	   PTHREAD_INHERIT_SCHED; that way the -p option to set realtime
    	   priority will propagate down to new threads by default.
    	   This does mean that callers cannot set a different priority using
    	   PTHREAD_EXPLICIT_SCHED in the attr argument; instead they must set
    	   the priority afterwards with pthread_setschedparam(). */
    
    	if ((errno = pthread_attr_setinheritsched(attr, PTHREAD_INHERIT_SCHED)))
    		ast_log(LOG_WARNING, "pthread_attr_setinheritsched: %s\n", strerror(errno));
    
    	if (!stacksize)
    		stacksize = AST_STACKSIZE;
    
    
    	if ((errno = pthread_attr_setstacksize(attr, stacksize ? stacksize : AST_STACKSIZE)))
    		ast_log(LOG_WARNING, "pthread_attr_setstacksize: %s\n", strerror(errno));
    
    
    #if !defined(LOW_MEMORY)
    
    	if ((a = ast_malloc(sizeof(*a)))) {
    
    		a->start_routine = start_routine;
    		a->data = data;
    		start_routine = dummy_start;
    
    		if (ast_asprintf(&a->name, "%-20s started at [%5d] %s %s()",
    
    			     start_fn, line, file, caller) < 0) {
    			a->name = NULL;
    		}
    
    #endif /* !LOW_MEMORY */
    
    	return pthread_create(thread, attr, start_routine, data); /* We're in ast_pthread_create, so it's okay */
    }
    
    
    int ast_pthread_create_detached_stack(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *),
    			     void *data, size_t stacksize, const char *file, const char *caller,
    			     int line, const char *start_fn)
    {
    	unsigned char attr_destroy = 0;
    	int res;
    
    	if (!attr) {
    
    		attr = ast_alloca(sizeof(*attr));
    
    		pthread_attr_init(attr);
    		attr_destroy = 1;
    	}
    
    	if ((errno = pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED)))
    		ast_log(LOG_WARNING, "pthread_attr_setdetachstate: %s\n", strerror(errno));
    
    
    	res = ast_pthread_create_stack(thread, attr, start_routine, data,
    
    	                               stacksize, file, caller, line, start_fn);
    
    	if (attr_destroy)
    		pthread_attr_destroy(attr);
    
    	return res;
    }
    
    
    int ast_wait_for_input(int fd, int ms)
    {
    	struct pollfd pfd[1];
    
    
    	memset(pfd, 0, sizeof(pfd));
    	pfd[0].fd = fd;
    	pfd[0].events = POLLIN | POLLPRI;
    	return ast_poll(pfd, 1, ms);
    }
    
    int ast_wait_for_output(int fd, int ms)
    {
    	struct pollfd pfd[1];
    
    
    	memset(pfd, 0, sizeof(pfd));
    	pfd[0].fd = fd;
    
    	return ast_poll(pfd, 1, ms);
    
    static int wait_for_output(int fd, int timeoutms)
    
    {
    	struct pollfd pfd = {
    		.fd = fd,
    		.events = POLLOUT,
    	};
    	int res;
    
    	struct timeval start = ast_tvnow();
    	int elapsed = 0;
    
    
    	/* poll() until the fd is writable without blocking */
    
    	while ((res = ast_poll(&pfd, 1, timeoutms - elapsed)) <= 0) {
    
    			ast_debug(1, "Timed out trying to write\n");
    
    			return -1;
    		} else if (res == -1) {
    			/* poll() returned an error, check to see if it was fatal */
    
    			if (errno == EINTR || errno == EAGAIN) {
    
    				elapsed = ast_tvdiff_ms(ast_tvnow(), start);
    				if (elapsed >= timeoutms) {
    					return -1;
    				}
    
    				/* This was an acceptable error, go back into poll() */
    				continue;
    			}
    
    			/* Fatal error, bail. */
    			ast_log(LOG_ERROR, "poll returned error: %s\n", strerror(errno));
    
    			return -1;
    		}
    
    		elapsed = ast_tvdiff_ms(ast_tvnow(), start);
    		if (elapsed >= timeoutms) {
    			return -1;
    		}
    
    /*!
     * Try to write string, but wait no more than ms milliseconds before timing out.
     *
     * \note The code assumes that the file descriptor has NONBLOCK set,
     * so there is only one system call made to do a write, unless we actually
     * have a need to wait.  This way, we get better performance.
     * If the descriptor is blocking, all assumptions on the guaranteed
     * detail do not apply anymore.
     */
    
    int ast_carefulwrite(int fd, char *s, int len, int timeoutms)
    
    	struct timeval start = ast_tvnow();
    
    		if (wait_for_output(fd, timeoutms - elapsed)) {
    
    
    		if (res < 0 && errno != EAGAIN && errno != EINTR) {
    			/* fatal error from write() */
    
    			if (errno == EPIPE) {
    #ifndef STANDALONE
    				ast_debug(1, "write() failed due to reading end being closed: %s\n", strerror(errno));
    #endif
    			} else {
    				ast_log(LOG_ERROR, "write() returned error: %s\n", strerror(errno));
    			}
    
    
    		if (res < 0) {
    			/* It was an acceptable error */
    
    		}
    
    		/* Update how much data we have left to write */
    
    
    		elapsed = ast_tvdiff_ms(ast_tvnow(), start);
    		if (elapsed >= timeoutms) {
    
    			/* We've taken too long to write
    
    			 * This is only an error condition if we haven't finished writing. */
    			res = len ? -1 : 0;
    			break;
    		}
    
    char *ast_strip_quoted(char *s, const char *beg_quotes, const char *end_quotes)
    {
    	char *e;
    	char *q;
    
    	s = ast_strip(s);
    
    	if ((q = strchr(beg_quotes, *s)) && *q != '\0') {
    
    		e = s + strlen(s) - 1;
    		if (*e == *(end_quotes + (q - beg_quotes))) {
    			s++;
    			*e = '\0';
    		}
    	}
    
    	return s;
    }
    
    
    char *ast_strsep(char **iss, const char sep, uint32_t flags)
    {
    	char *st = *iss;
    	char *is;
    	int inquote = 0;
    	int found = 0;
    	char stack[8];
    
    
    	if (ast_strlen_zero(st)) {
    
    		return NULL;
    	}
    
    	memset(stack, 0, sizeof(stack));
    
    	for(is = st; *is; is++) {
    		if (*is == '\\') {
    			if (*++is != '\0') {
    				is++;
    			} else {
    				break;
    			}
    		}
    
    		if (*is == '\'' || *is == '"') {
    			if (*is == stack[inquote]) {
    				stack[inquote--] = '\0';
    			} else {
    				if (++inquote >= sizeof(stack)) {
    					return NULL;
    				}
    				stack[inquote] = *is;
    			}
    		}
    
    		if (*is == sep && !inquote) {
    			*is = '\0';
    			found = 1;
    			*iss = is + 1;
    			break;
    		}
    	}
    	if (!found) {
    		*iss = NULL;
    	}
    
    	if (flags & AST_STRSEP_STRIP) {
    		st = ast_strip_quoted(st, "'\"", "'\"");
    	}
    
    	if (flags & AST_STRSEP_TRIM) {
    		st = ast_strip(st);
    	}
    
    	if (flags & AST_STRSEP_UNESCAPE) {
    		ast_unescape_quoted(st);
    	}
    
    	return st;
    }
    
    
    char *ast_unescape_semicolon(char *s)
    {
    	char *e;
    	char *work = s;
    
    	while ((e = strchr(work, ';'))) {
    		if ((e > work) && (*(e-1) == '\\')) {
    			memmove(e - 1, e, strlen(e) + 1);
    			work = e;
    
    		} else {
    			work = e + 1;
    
    /* !\brief unescape some C sequences in place, return pointer to the original string.
     */
    char *ast_unescape_c(char *src)
    {
    	char c, *ret, *dst;
    
    	if (src == NULL)
    		return NULL;
    	for (ret = dst = src; (c = *src++); *dst++ = c ) {
    		if (c != '\\')
    			continue;	/* copy char at the end of the loop */
    		switch ((c = *src++)) {
    		case '\0':	/* special, trailing '\' */
    			c = '\\';
    			break;
    		case 'b':	/* backspace */
    			c = '\b';
    			break;
    		case 'f':	/* form feed */
    			c = '\f';
    			break;
    		case 'n':
    			c = '\n';
    			break;
    		case 'r':
    			c = '\r';
    			break;
    		case 't':
    			c = '\t';
    			break;
    		}
    		/* default, use the char literally */
    	}
    	*dst = '\0';
    	return ret;
    }
    
    
    /*
     * Standard escape sequences - Note, '\0' is not included as a valid character
     * to escape, but instead is used here as a NULL terminator for the string.
     */
    char escape_sequences[] = {
    	'\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\', '\'', '\"', '\?', '\0'
    };
    
    /*
     * Standard escape sequences output map (has to maintain matching order with
     * escape_sequences). '\0' is included here as a NULL terminator for the string.
     */
    static char escape_sequences_map[] = {
    	'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"', '?', '\0'
    };
    
    
    char *ast_escape(char *dest, const char *s, size_t size, const char *to_escape)
    
    {
    	char *p;
    
    	if (!dest || !size) {
    		return dest;
    	}
    	if (ast_strlen_zero(s)) {
    		*dest = '\0';
    
    		return dest;
    	}
    
    	if (ast_strlen_zero(to_escape)) {
    
    		ast_copy_string(dest, s, size);
    
    	for (p = dest; *s && --size; ++s, ++p) {
    
    		/* If in the list of characters to escape then escape it */
    		if (strchr(to_escape, *s)) {
    
    			if (!--size) {
    				/* Not enough room left for the escape sequence. */
    				break;
    			}
    
    
    			/*
    			 * See if the character to escape is part of the standard escape
    			 * sequences. If so we'll have to use its mapped counterpart
    			 * otherwise just use the current character.
    			 */
    
    			c = strchr(escape_sequences, *s);
    
    			*p++ = '\\';
    			*p = c ? escape_sequences_map[c - escape_sequences] : *s;
    		} else {
    			*p = *s;
    		}
    	}
    	*p = '\0';
    
    char *ast_escape_c(char *dest, const char *s, size_t size)
    
    {
    	/*
    	 * Note - This is an optimized version of ast_escape. When looking only
    	 * for escape_sequences a couple of checks used in the generic case can
    	 * be left out thus making it slightly more efficient.
    	 */
    	char *p;
    
    	if (!dest || !size) {
    		return dest;
    	}
    	if (ast_strlen_zero(s)) {
    		*dest = '\0';
    
    	for (p = dest; *s && --size; ++s, ++p) {
    
    		/*
    		 * See if the character to escape is part of the standard escape
    		 * sequences. If so use its mapped counterpart.
    		 */
    
    		c = strchr(escape_sequences, *s);
    
    		if (c) {