mirror of
				https://github.com/alsa-project/alsa-lib.git
				synced 2025-11-03 09:01:52 -05:00 
			
		
		
		
	conf: Add thread-safe global tree reference
Most of open functions in alsa-lib have the call pattern: snd_config_update(); return snd_xxx_open(x, snd_config, ...); This means that the toplevel config gets updated, and passed to a local open function. Although snd_config_update() itself has a pthread mutex to be thread safe, the whole procedure above isn't thread safe. Namely, the global snd_config tree may be deleted and recreated at any time while the open function is being processed. This may lead to a data corruption and crash of the program. For avoiding the corruption, this patch introduces a refcount to config tree object. A few new helper functions are introduced as well: - snd_config_update_ref() does update and take the refcount of the toplevel tree. The obtained config tree has to be freed via snd_config_unref() below. - snd_config_ref() and snd_config_unref() manage the refcount of the config object. The latter eventually deletes the object when all references are gone. Along with these additions, the caller of snd_config_update() and snd_config global tree in alsa-lib are replaced with the new helpers. Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
		
							parent
							
								
									5fb3fe1724
								
							
						
					
					
						commit
						c9a0d7d601
					
				
					 9 changed files with 125 additions and 14 deletions
				
			
		
							
								
								
									
										79
									
								
								src/conf.c
									
										
									
									
									
								
							
							
						
						
									
										79
									
								
								src/conf.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -434,6 +434,7 @@ static pthread_once_t snd_config_update_mutex_once = PTHREAD_ONCE_INIT;
 | 
			
		|||
struct _snd_config {
 | 
			
		||||
	char *id;
 | 
			
		||||
	snd_config_type_t type;
 | 
			
		||||
	int refcount; /* default = 0 */
 | 
			
		||||
	union {
 | 
			
		||||
		long integer;
 | 
			
		||||
		long long integer64;
 | 
			
		||||
| 
						 | 
				
			
			@ -1825,6 +1826,10 @@ int snd_config_remove(snd_config_t *config)
 | 
			
		|||
 * If the node is a compound node, its descendants (the whole subtree)
 | 
			
		||||
 * are deleted recursively.
 | 
			
		||||
 *
 | 
			
		||||
 * The function is supposed to be called only for locally copied config
 | 
			
		||||
 * trees.  For the global tree, take the reference via #snd_config_update_ref
 | 
			
		||||
 * and free it via #snd_config_unref.
 | 
			
		||||
 *
 | 
			
		||||
 * \par Conforming to:
 | 
			
		||||
 * LSB 3.2
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			@ -1833,6 +1838,10 @@ int snd_config_remove(snd_config_t *config)
 | 
			
		|||
int snd_config_delete(snd_config_t *config)
 | 
			
		||||
{
 | 
			
		||||
	assert(config);
 | 
			
		||||
	if (config->refcount > 0) {
 | 
			
		||||
		config->refcount--;
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	switch (config->type) {
 | 
			
		||||
	case SND_CONFIG_TYPE_COMPOUND:
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -3833,6 +3842,8 @@ int snd_config_update_r(snd_config_t **_top, snd_config_update_t **_update, cons
 | 
			
		|||
 * \warning Whenever #snd_config is updated, all string pointers and
 | 
			
		||||
 * configuration node handles previously obtained from it may become
 | 
			
		||||
 * invalid.
 | 
			
		||||
 * For safer operations, use #snd_config_update_ref and release the config
 | 
			
		||||
 * via #snd_config_unref.
 | 
			
		||||
 *
 | 
			
		||||
 * \par Errors:
 | 
			
		||||
 * Any errors encountered when parsing the input or returned by hooks or
 | 
			
		||||
| 
						 | 
				
			
			@ -3851,6 +3862,74 @@ int snd_config_update(void)
 | 
			
		|||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Updates #snd_config and takes its reference.
 | 
			
		||||
 * \return 0 if #snd_config was up to date, 1 if #snd_config was
 | 
			
		||||
 *         updated, otherwise a negative error code.
 | 
			
		||||
 *
 | 
			
		||||
 * Unlike #snd_config_update, this function increases a reference counter
 | 
			
		||||
 * so that the obtained tree won't be deleted until unreferenced by
 | 
			
		||||
 * #snd_config_unref.
 | 
			
		||||
 *
 | 
			
		||||
 * This function is supposed to be thread-safe.
 | 
			
		||||
 */
 | 
			
		||||
int snd_config_update_ref(snd_config_t **top)
 | 
			
		||||
{
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	if (top)
 | 
			
		||||
		*top = NULL;
 | 
			
		||||
	snd_config_lock();
 | 
			
		||||
	err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);
 | 
			
		||||
	if (err >= 0) {
 | 
			
		||||
		if (snd_config) {
 | 
			
		||||
			if (top) {
 | 
			
		||||
				snd_config->refcount++;
 | 
			
		||||
				*top = snd_config;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			err = -ENODEV;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	snd_config_unlock();
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Take the reference of the config tree.
 | 
			
		||||
 *
 | 
			
		||||
 * Increases a reference counter of the given config tree.
 | 
			
		||||
 *
 | 
			
		||||
 * This function is supposed to be thread-safe.
 | 
			
		||||
 */
 | 
			
		||||
void snd_config_ref(snd_config_t *cfg)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_lock();
 | 
			
		||||
	if (cfg)
 | 
			
		||||
		cfg->refcount++;
 | 
			
		||||
	snd_config_unlock();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Unreference the config tree.
 | 
			
		||||
 *
 | 
			
		||||
 * Decreases a reference counter of the given config tree, and eventually
 | 
			
		||||
 * deletes the tree if all references are gone.  This is the counterpart of
 | 
			
		||||
 * #snd_config_unref.
 | 
			
		||||
 *
 | 
			
		||||
 * Also, the config taken via #snd_config_update_ref must be unreferenced
 | 
			
		||||
 * by this function, too.
 | 
			
		||||
 *
 | 
			
		||||
 * This function is supposed to be thread-safe.
 | 
			
		||||
 */
 | 
			
		||||
void snd_config_unref(snd_config_t *cfg)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_lock();
 | 
			
		||||
	if (cfg)
 | 
			
		||||
		snd_config_delete(cfg);
 | 
			
		||||
	snd_config_unlock();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 
 | 
			
		||||
 * \brief Frees a private update structure.
 | 
			
		||||
 * \param[in] update The private update structure to free.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -968,12 +968,16 @@ static int snd_ctl_open_noupdate(snd_ctl_t **ctlp, snd_config_t *root, const cha
 | 
			
		|||
 */
 | 
			
		||||
int snd_ctl_open(snd_ctl_t **ctlp, const char *name, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert(ctlp && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_ctl_open_noupdate(ctlp, snd_config, name, mode);
 | 
			
		||||
	err = snd_ctl_open_noupdate(ctlp, top, name, mode);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -168,12 +168,16 @@ static int snd_hwdep_open_noupdate(snd_hwdep_t **hwdep, snd_config_t *root, cons
 | 
			
		|||
 */
 | 
			
		||||
int snd_hwdep_open(snd_hwdep_t **hwdep, const char *name, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert(hwdep && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_hwdep_open_noupdate(hwdep, snd_config, name, mode);
 | 
			
		||||
	err = snd_hwdep_open_noupdate(hwdep, top, name, mode);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2288,12 +2288,16 @@ static int snd_pcm_open_noupdate(snd_pcm_t **pcmp, snd_config_t *root,
 | 
			
		|||
int snd_pcm_open(snd_pcm_t **pcmp, const char *name, 
 | 
			
		||||
		 snd_pcm_stream_t stream, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert(pcmp && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_pcm_open_noupdate(pcmp, snd_config, name, stream, mode, 0);
 | 
			
		||||
	err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -305,12 +305,16 @@ static int snd_rawmidi_open_noupdate(snd_rawmidi_t **inputp, snd_rawmidi_t **out
 | 
			
		|||
int snd_rawmidi_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
 | 
			
		||||
		     const char *name, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert((inputp || outputp) && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_rawmidi_open_noupdate(inputp, outputp, snd_config, name, mode);
 | 
			
		||||
	err = snd_rawmidi_open_noupdate(inputp, outputp, top, name, mode);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -974,12 +974,16 @@ static int snd_seq_open_noupdate(snd_seq_t **seqp, snd_config_t *root,
 | 
			
		|||
int snd_seq_open(snd_seq_t **seqp, const char *name, 
 | 
			
		||||
		 int streams, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert(seqp && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_seq_open_noupdate(seqp, snd_config, name, streams, mode, 0);
 | 
			
		||||
	err = snd_seq_open_noupdate(seqp, top, name, streams, mode, 0);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -201,12 +201,16 @@ static int snd_timer_open_noupdate(snd_timer_t **timer, snd_config_t *root, cons
 | 
			
		|||
 */
 | 
			
		||||
int snd_timer_open(snd_timer_t **timer, const char *name, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert(timer && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_timer_open_noupdate(timer, snd_config, name, mode);
 | 
			
		||||
	err = snd_timer_open_noupdate(timer, top, name, mode);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,12 +159,16 @@ static int snd_timer_query_open_noupdate(snd_timer_query_t **timer, snd_config_t
 | 
			
		|||
 */
 | 
			
		||||
int snd_timer_query_open(snd_timer_query_t **timer, const char *name, int mode)
 | 
			
		||||
{
 | 
			
		||||
	snd_config_t *top;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	assert(timer && name);
 | 
			
		||||
	err = snd_config_update();
 | 
			
		||||
	err = snd_config_update_ref(&top);
 | 
			
		||||
	if (err < 0)
 | 
			
		||||
		return err;
 | 
			
		||||
	return snd_timer_query_open_noupdate(timer, snd_config, name, mode);
 | 
			
		||||
	err = snd_timer_query_open_noupdate(timer, top, name, mode);
 | 
			
		||||
	snd_config_unref(top);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue