Esempio n. 1
0
int
pdict_walk(pdict_t *pd, pdict_walk_func_t w, void *arg)
{
	void *args[2] = { (void *)w, arg };

	return ptree_walk(pd->pd_ents, PTREE_INORDER, pdict_walk_cb, NULL, args);
}
Esempio n. 2
0
static int
_pdict_walk_int(pdict_t *pd, pdict_walk_int_func_t w, void *arg)
{
	void *args[2] = { (void *)w, arg };

	return ptree_walk(pd->pd_ents, PTREE_INORDER, pdict_walk_int_cb, pdecmp, args);
}
Esempio n. 3
0
/*
 * This is started by accept_cb as a thread on a new connection.
 * Calls pd_getline and sends the data it gets to pds_process_line
 * until pd_getline > 0 and !pdss->pdss_should_close
 */
int
pds_session_serve(const pds_session_t *arg)
{
	pds_session_t *pdss = (pds_session_t *)arg;
	char *line = NULL;
	int res;
	
#ifdef _MACOSX
	signal (SIGPIPE, SIG_IGN);
#endif
	
	DPRINT(PUL_INFO, "Started new pds_session_serve thread");
	
	pdss->pdss_errdesc[0] = 0;
	
	/* first thing - Authenticate */
	if(pdss->pdss_auth)
	{
		if(pdss->pdss_auth((pds_session_t *)pdss)){
			pu_log(PUL_WARN, pdss->pdss_id, "Authentication failed or bad version - closing connection");
			goto authfailed;
		}
	}
	
	while ((res = pd_getline(pdss->pdss_readbuf, sizeof (pdss->pdss_readbuf),
							 &pdss->pdss_bufcur, &pdss->pdss_buflen, pdss->pdss_read,
							 pdss->pdss_close, pdss->pdss_rfd, &line, pdss->pdss_errdesc,
							 sizeof (pdss->pdss_errdesc))) > 0 && !pdss->pdss_should_close)
	{
		pds_process_line((pds_session_t *)pdss, line);
		free(line); line=NULL;
	}
	
authfailed:
	
	free(line); line=NULL;
	
	//Shut down the report thread
	pdss->pdss_should_close = 1;

	pthread_mutex_lock(&pdss->pdss_lock);
	if (pdss->pdss_report_thread)
	{
		void *status;
		//pthread_cond_wait(&pdss->pdss_report_cv, &pdss->pdss_lock);
		pthread_mutex_unlock(&pdss->pdss_lock);
		pthread_join(pdss->pdss_report_thread, &status);
		pthread_mutex_lock(&pdss->pdss_lock);
	}
	
	/* remove keys set to expire at end of session */
	pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
	plist_walk(pdss->pdss_expire, expired_key_cb, pdss->pdss_pd);
	plist_clear(&pdss->pdss_expire);
	
	/* write_pending will free pending notifications - it will NOT send them because we freed pdss_write */
	pdss->pdss_write = NULL;

	ptree_walk(pdss->pdss_pending, PTREE_POSTORDER, write_pending, ipmcmp, pdss);
	assert(!pdss->pdss_pending);
	//ptree_clear(&pdss->pdss_pending);
	//pdss->pdss_pending = NULL;
	
	//clear the listener list - need to lock the dictionary, 
	//because an add/remove could be enumerating the list as we free it otherwise.
	ptree_walk(pdss->pdss_notify_args, PTREE_POSTORDER, free_na_cb, nacmp, pdss);
	//ptree_clear(&pdss->pdss_notify_args);
	assert(!pdss->pdss_notify_args);
	
	//Close the socket
	pdss->pdss_close(pdss->pdss_wfd, NULL, 0);
	if(pdss->pdss_wfd != pdss->pdss_rfd)
		pdss->pdss_close(pdss->pdss_rfd, NULL, 0);
	pdss->pdss_wfd = pdss->pdss_rfd = INVALID_SOCKET;
	pdss->pdss_close = NULL;
	
	pu_log(PUL_INFO, pdss->pdss_id, "done - session closed");
	
	pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
	pthread_mutex_unlock(&pdss->pdss_lock);
	
	//make sure no cb threads are running
	while(pdss->cb_threads_count)
		SLEEP(10);

	/* now free the pdss!! */
	pds_session_free(pdss);
	
	pu_log(PUL_INFO, 0, "Exiting pds_session_serve thread");
	return 0;
}
Esempio n. 4
0
void
pds_process_line(pds_session_t *pdss, char *line)
{
	regmatch_t pmatch[9];
	char *cmdtag = NULL;
	char *cmdstr = NULL;
	char *key = NULL;
	char *val = NULL;
	char *buf = NULL;
	char *listenid = NULL;
	int reportperiod;
	int forsession;
	int res;
	
	//DPRINT(line);
	
	if ((res = regexec(&setex, line, 7, pmatch, 0)) == 0) {
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			free(cmdtag); cmdtag = NULL;
			return;
		}
		if (!getmatchsub(line, &key, pmatch, 2) || !key)
			goto fail;
		
		if (!getmatchsub(line, &val, pmatch, 5) &&
		    !getmatchsub(line, &val, pmatch, 4))
			goto fail;
		if (!val)
			goto fail;
		if ((forsession = getmatchsub(line, NULL, pmatch, 6)) > 0) {
			/* mark for expiration, but make sure it's only added once! */
			if(plist_walk(pdss->pdss_expire, is_key_in_list, key))
			{
				char *e;
				if (!(e = strdup(key)))
					goto fail;
				plist_add(e, NULL, &pdss->pdss_expire);
			}
		}
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		res = pdict_add(pdss->pdss_pd, key, val, NULL);
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		if (!res)
			result(pdss, cmdtag, "304 set failed");
		else
			result(pdss, cmdtag, "200 set successful");
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		free(key); key = NULL;
		free(val); val = NULL;
		return;
	}
	if ((res = regexec(&listenex, line, 6, pmatch, 0)) == 0) {
		notify_arg_t *nap;
		notify_arg_t na;
		
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			free(cmdtag); cmdtag = NULL;
			return;
		}
		if ((!getmatchsub(line, &buf, pmatch, 4) && !getmatchsub(line, &buf, pmatch, 3)) || !buf)
			goto fail;
		getmatchsub(line, &listenid, pmatch, 5);
		na.na_pdss = pdss;
		na.na_listenid = listenid;
		pthread_mutex_lock(&pdss->pdss_lock);
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		if (!ptree_contains(&na, pdss->pdss_notify_args, nacmp, (void **)&nap)) {
			if (!(nap = malloc(sizeof (*nap))))
			{
				pu_log(PUL_WARN, pdss->pdss_id, "insufficient memory");
				pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
				pthread_mutex_unlock(&pdss->pdss_lock);
				goto fail;
			}
			nap->na_pdss = pdss;
			nap->na_listenid = listenid;
			nap->na_pd_ids = NULL;
			if (!ptree_replace(nap, &pdss->pdss_notify_args, nacmp, NULL)) {
				free(nap); nap = NULL;
				pu_log(PUL_WARN, pdss->pdss_id, "insufficient memory");
				pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
				pthread_mutex_unlock(&pdss->pdss_lock);
				free(listenid); listenid = NULL;
				free(buf); buf = NULL;
				goto fail;
			}
		}
		
		if (!(res = pdict_add_persistent_change_listener(pdss->pdss_pd, buf, notify, (void *)nap))) {
			if (!nap->na_pd_ids) {
				ptree_remove(nap, &pdss->pdss_notify_args, nacmp, NULL);
				free((void *)nap->na_listenid); nap->na_listenid = NULL;
				free(nap); nap = NULL;
			}
			result(pdss, cmdtag, "303 listen not established--bad pattern?");
		} else {
			if (!plist_add((void *)res, NULL, &nap->na_pd_ids)) {
				pdict_remove_persistent_change_listener(pdss->pdss_pd, res);
				if (!nap->na_pd_ids) {
					ptree_remove(nap, &pdss->pdss_notify_args, nacmp, NULL);
					free((void *)nap->na_listenid); nap->na_listenid = NULL;
					free(nap); nap = NULL;
				}
				free(buf); buf = NULL;
				if (cmdtag) {
					free(cmdtag); cmdtag = NULL;
				}
				pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
				pthread_mutex_unlock(&pdss->pdss_lock);
				goto fail;
			} else {
				result(pdss, cmdtag,"200 listening, id %s",
					   listenid);
			}
		}
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		pthread_mutex_unlock(&pdss->pdss_lock);
		free(buf); buf = NULL;
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		return;
	}
	if ((res = regexec(&reportex, line, 5, pmatch, 0)) == 0) {
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			free(cmdtag); cmdtag = NULL;
			return;
		}
		if (!getmatchsub(line, &buf, pmatch, 2) || !buf)
			goto fail;
		if ((reportperiod = atoi(buf)) < 0 || reportperiod > 10000) {
			result(pdss, cmdtag, "301 invalid report/wait period"
				   "--specify milliseconds or 0 for off (max 10s)");
			if (cmdtag) {
				free(cmdtag); cmdtag = NULL;
			}
			free(buf); buf = NULL;
			return;
		}
		free(buf); buf = NULL;
		getmatchsub(line, &buf, pmatch, 3);
		set_report_period(pdss, cmdtag, buf, reportperiod);
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		if (buf) {
			free(buf); buf = NULL;
		}
		return;
	}
	if ((res = regexec(&waitex, line, 5, pmatch, 0)) == 0) {
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			free(cmdtag); cmdtag = NULL;
			return;
		}
		if (!getmatchsub(line, &buf, pmatch, 2) || !buf)
			goto fail;
		if (atoi(buf) < 0 || atoi(buf) > 10000) {
			result(pdss, cmdtag, "301 invalid wait period"
				   "--specify milliseconds (max 10s)");
			if (cmdtag) {
				free(cmdtag); cmdtag = NULL;
			}
			return;
		}
		usleep(atoi(buf) * 1000);
		result(pdss, cmdtag, "200 nothin' doin'");
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		return;
	}
	if ((res = regexec(&flushex, line, 5, pmatch, 0)) == 0) {
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			free(cmdtag); cmdtag = NULL;
			return;
		}
		pthread_mutex_lock(&pdss->pdss_lock);
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		_flush(pdss);
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		pthread_mutex_unlock(&pdss->pdss_lock);
		result(pdss, cmdtag, "200 glug glug");
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		return;
	}
	if ((res = regexec(&walkex, line, 5, pmatch, 0)) == 0) {
		wa_t wa;
		
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			free(cmdtag); cmdtag = NULL;
			return;
		}
		if (!getmatchsub(line, &cmdstr, pmatch, 2) || !cmdstr)
			goto fail;
		if (!getmatchsub(line, &buf, pmatch, 3) || !buf) {
			free(cmdstr); cmdstr = NULL;
			goto fail;
		}
		if ((regcomp(&wa.wa_regex, buf, REG_EXTENDED)) != 0) {
			free(cmdstr); cmdstr = NULL;
			free(buf); buf = NULL;
			result(pdss, cmdtag, "305 expression error"); 
			if (cmdtag) {
				free(cmdtag); cmdtag = NULL;
			}
			return;
		}
		wa.wa_pdss = pdss;
		wa.wa_cmdtag = cmdtag;
		wa.wa_l = NULL;
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		if (!pdict_walk(pdss->pdss_pd, add_to_wa_list, &wa)) {
			int e = errno;
			pu_log(PUL_WARN, pdss->pdss_id, "temporary failure: %s", strerror(e));
			result(pdss, cmdtag, "300 temporary failure: %s",
				   strerror(e)); 
		} else {
			if (strcmp(cmdstr, "remove") == 0)
				plist_walk(wa.wa_l, remove_wa_list, &wa);
			else
				plist_walk(wa.wa_l, print_wa_list, &wa);
			result(pdss, cmdtag, "200 done");
		}
		plist_clear(&wa.wa_l);
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		regfree(&wa.wa_regex);
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		free(cmdstr); cmdstr = NULL;
		free(buf); buf = NULL;
		return;
	}
	if ((res = regexec(&ignoreex, line, 5, pmatch, 0)) == 0) {
		notify_arg_t *nap;
		notify_arg_t na;
		void *arg[2];
		int n;
		
		getmatchsub(line, &cmdtag, pmatch, 1);
		if (cmdtag && strcmp(cmdtag, "unknown") == 0) {
			result(pdss, cmdtag,
				   "304 unknown is a reserved command tag");
			if (cmdtag) {
				free(cmdtag); cmdtag = NULL;
			}
			return;
		}
		if (!getmatchsub(line, &listenid, pmatch, 2) || !listenid)
			goto fail;
		pthread_mutex_lock(&pdss->pdss_lock);
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		na.na_pdss = pdss;
		na.na_listenid = listenid;
		if (!ptree_remove(&na, &pdss->pdss_notify_args, nacmp,
						  (void **)&nap)) {
			pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
			pthread_mutex_unlock(&pdss->pdss_lock);
			result(pdss, cmdtag, "306 nonexistent key/id");
			if (cmdtag) {
				free(cmdtag); cmdtag = NULL;
			}
			return;
		}
		n = 0;
		plist_walk(nap->na_pd_ids, _count, &n);
		plist_walk(nap->na_pd_ids, remove_persistent_change_listener_cb,
				   pdss->pdss_pd);
		arg[0] = (void *)nap->na_listenid;
		arg[1] = &pdss->pdss_pending;
		ptree_walk(pdss->pdss_pending, PTREE_POSTORDER, remove_pending_id, ipmcmp, arg);
		free((void *)nap->na_listenid); nap->na_listenid = NULL;
		plist_clear(&nap->na_pd_ids);
		free(nap); nap = NULL;
		assert(!ptree_contains(&na, pdss->pdss_notify_args, nacmp, NULL));
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		pthread_mutex_unlock(&pdss->pdss_lock);
		
		result(pdss, cmdtag, "200 %d listener%s ignored", n, n > 1 ? "s"
			   : "");
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		free(listenid);
		return;
	}
	if ((res = regexec(&quitex, line, 5, pmatch, 0)) == 0) {
		getmatchsub(line, &cmdtag, pmatch, 1);
		result(pdss, cmdtag, "200 goodbye");
		pdss->pdss_should_close = 1;
		pdss->pdss_close(pdss->pdss_wfd, NULL, 0);
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		return;
	}
	if ((res = regexec(&getidex, line, 5, pmatch, 0)) == 0) {
		getmatchsub(line, &cmdtag, pmatch, 1);
		result(pdss, cmdtag, "200 %d", pdss->pdss_id);
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		return;
	}
	if ((res = regexec(&okex, line, 5, pmatch, 0)) == 0) {
		if (cmdtag) {
			free(cmdtag); cmdtag = NULL;
		}
		return;
	}
	
	result(pdss, NULL, "400 input unrecognized: %s", line);
	return;
fail:
	result(pdss, cmdtag, "300 command failed: %s", strerror(errno));
	if (cmdtag)
		free (cmdtag);
}
Esempio n. 5
0
static void *
report(void *arg)
{
	pds_session_t *pdss = arg;
	char *cmdtag = NULL;
	int period = 0;
	
	pthread_mutex_lock(&pdss->pdss_lock);
	do {
		/* XXX not periodic */
		/* XXX if expires with nothing pending, wait on condvar */
		period = pdss->pdss_report_period;
		if (period) {
			pthread_mutex_unlock(&pdss->pdss_lock);
			usleep(period * 1000);
			pthread_mutex_lock(&pdss->pdss_lock);
			period = pdss->pdss_report_period;
		}
		if ((cmdtag && pdss->pdss_report_cmdtag && strcmp(cmdtag,
														  pdss->pdss_report_cmdtag) != 0) || (!cmdtag &&
																							  pdss->pdss_report_cmdtag)) {
			if (cmdtag && strcmp(cmdtag, "unknown") != 0)
			{
				free(cmdtag); cmdtag = NULL;
			}
			cmdtag = strdup(pdss->pdss_report_cmdtag);
			if (!cmdtag)
				cmdtag = "unknown";
		} else if (cmdtag && !pdss->pdss_report_cmdtag) {
			if (strcmp(cmdtag, "unknown") != 0)
				free(cmdtag);
			cmdtag = NULL;
		}
		if (!period)
			break;
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		if (!pdss->pdss_pending) {
			pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
			continue;
		}
		result(pdss, cmdtag, "200-periodic report follows:");
		pthread_mutex_unlock(&pdss->pdss_lock);
		ptree_walk(pdss->pdss_pending, PTREE_POSTORDER, write_pending, ipmcmp, pdss);
		assert(!pdss->pdss_pending);
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		result(pdss, cmdtag, "200-that's all for now");
		pthread_mutex_lock(&pdss->pdss_lock);
		period = pdss->pdss_report_period;
		pthread_cond_signal(&pdss->pdss_report_cv);
		pdss->pdss_nreport++;
	} while (period && !pdss->pdss_should_close);
	if (cmdtag && strcmp(cmdtag, "unknown") != 0)
		free(cmdtag);
	cmdtag = NULL;
	if (pdss->pdss_report_cmdtag) {
		free(pdss->pdss_report_cmdtag); pdss->pdss_report_cmdtag = NULL;
	}
	pthread_mutex_unlock(&pdss->pdss_lock);
	pthread_cond_signal(&pdss->pdss_report_cv);
	
	//Don't just set this to 0! we won't be able to join it.
	//pdss->pdss_report_thread = 0;
	
	pu_log(PUL_INFO, 0, "Exiting report thread");
	return 0;
}
Esempio n. 6
0
/*
 * This is started by accept_cb as a thread on a new connection.
 * Calls pd_getline and sends the data it gets to pds_process_line
 * until pd_getline > 0 and !pdss->pdss_should_close
 */
int
pds_session_serve(const pds_session_t *arg)
{
	pds_session_t *pdss = (pds_session_t *)arg;
	char *line;
	int res;
	
#ifdef _MACOSX
	signal (SIGPIPE, SIG_IGN);
#endif

	pdss->pdss_errdesc[0] = 0;
	while ((res = pd_getline(pdss->pdss_readbuf, sizeof (pdss->pdss_readbuf),
	    &pdss->pdss_bufcur, &pdss->pdss_buflen, pdss->pdss_read,
	    pdss->pdss_close, pdss->pdss_rfd, &line, pdss->pdss_errdesc,
	    sizeof (pdss->pdss_errdesc))) > 0 && !pdss->pdss_should_close)
	{
		pds_process_line((pds_session_t *)pdss, line);
		free(line); line=NULL;
	}
	
	free(line); line=NULL;
	pdss->pdss_should_close = 1;

	/*printf("Printing out expiring keys:\n");
	plist_walk(pdss->pdss_expire, print_key, NULL);
	printf("DONE\n");*/

	/* remove keys set to expire at end of session */
	pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
	plist_walk(pdss->pdss_expire, expired_key_cb, pdss->pdss_pd);
	plist_clear(&pdss->pdss_expire);
	pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);

	pthread_mutex_lock(&pdss->pdss_lock);
	if (pdss->pdss_report_thread)
	{
		void *status;
		//pthread_cond_wait(&pdss->pdss_report_cv, &pdss->pdss_lock);
		pthread_mutex_unlock(&pdss->pdss_lock);
		pthread_join(pdss->pdss_report_thread, &status);
		pthread_mutex_lock(&pdss->pdss_lock);
	}
	ptree_walk(pdss->pdss_notify_args, PTREE_INORDER, free_na_cb, NULL);
	ptree_clear(&pdss->pdss_notify_args);
	pthread_mutex_unlock(&pdss->pdss_lock);

	/* write_pending will free pending notifications */
	pdss->pdss_write = NULL;
	ptree_walk(pdss->pdss_pending, PTREE_INORDER, write_pending, pdss);
	ptree_clear(&pdss->pdss_pending);
	
	assert(!pdss->pdss_pending);
	assert(!pdss->pdss_notify_args);
	pu_log(PUL_INFO, pdss->pdss_id, "done - session closed");
	
	/* now free the pdss!! */
	pds_session_free(pdss);

	return res;
}
Esempio n. 7
0
static void *
report(void *arg)
{
	pds_session_t *pdss = arg;
	char *cmdtag = NULL;
	ptree_node_t *cur;
	int period = 0;

	pthread_mutex_lock(&pdss->pdss_lock);
	do {
		/* XXX not periodic */
		/* XXX if expires with nothing pending, wait on condvar */
		period = pdss->pdss_report_period;
		if (period) {
			pthread_mutex_unlock(&pdss->pdss_lock);
			usleep(period * 1000);
			pthread_mutex_lock(&pdss->pdss_lock);
			period = pdss->pdss_report_period;
		}
		if ((cmdtag && pdss->pdss_report_cmdtag && strcmp(cmdtag,
		    pdss->pdss_report_cmdtag) != 0) || (!cmdtag &&
		    pdss->pdss_report_cmdtag)) {
			if (cmdtag && strcmp(cmdtag, "unknown") != 0)
			{
				free(cmdtag); cmdtag = NULL;
			}
			cmdtag = strdup(pdss->pdss_report_cmdtag);
			if (!cmdtag)
				cmdtag = "unknown";
		} else if (cmdtag && !pdss->pdss_report_cmdtag) {
			if (strcmp(cmdtag, "unknown") != 0)
				free(cmdtag);
			cmdtag = NULL;
		}
		if (!period)
			break;
		pdss->pdss_pd_lock(pdss->pdss_pd_lock_arg);
		if (!pdss->pdss_pending) {
			pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
			continue;
		}
		cur = pdss->pdss_pending;
		pdss->pdss_pending = NULL;
		pdss->pdss_pd_unlock(pdss->pdss_pd_lock_arg);
		result(pdss, cmdtag, "200-periodic report follows:");
		pthread_mutex_unlock(&pdss->pdss_lock);
		ptree_walk(cur, PTREE_INORDER, write_pending, pdss);
		ptree_clear(&cur);
		result(pdss, cmdtag, "200-that's all for now");
		pthread_mutex_lock(&pdss->pdss_lock);
		period = pdss->pdss_report_period;
		pthread_cond_signal(&pdss->pdss_report_cv);
		pdss->pdss_nreport++;
	} while (period && !pdss->pdss_should_close);
	if (cmdtag && strcmp(cmdtag, "unknown") != 0)
		free(cmdtag);
	cmdtag = NULL;
	if (pdss->pdss_report_cmdtag) {
		free(pdss->pdss_report_cmdtag); pdss->pdss_report_cmdtag = NULL;
	}
	pthread_mutex_unlock(&pdss->pdss_lock);
	pthread_cond_signal(&pdss->pdss_report_cv);
	pdss->pdss_report_thread = 0;

	return 0;
}