mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-31 22:25:35 -04:00
added ipc_gid for direct plugins
This commit is contained in:
parent
d7ef50f5ee
commit
4b7e3cff81
5 changed files with 107 additions and 8 deletions
|
|
@ -50,10 +50,21 @@
|
||||||
|
|
||||||
int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix)
|
int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix)
|
||||||
{
|
{
|
||||||
|
struct shmid_ds buf;
|
||||||
|
|
||||||
dmix->semid = semget(dmix->ipc_key, DIRECT_IPC_SEMS,
|
dmix->semid = semget(dmix->ipc_key, DIRECT_IPC_SEMS,
|
||||||
IPC_CREAT | dmix->ipc_perm);
|
IPC_CREAT | dmix->ipc_perm);
|
||||||
if (dmix->semid < 0)
|
if (dmix->semid < 0)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
if (dmix->ipc_gid < 0)
|
||||||
|
return 0;
|
||||||
|
if (shmctl(dmix->semid, IPC_STAT, &buf) < 0) {
|
||||||
|
int err = -errno;
|
||||||
|
snd_pcm_direct_semaphore_discard(dmix);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
buf.shm_perm.gid = dmix->ipc_gid;
|
||||||
|
shmctl(dmix->semid, IPC_SET, &buf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +131,10 @@ retryget:
|
||||||
}
|
}
|
||||||
if (buf.shm_nattch == 1) { /* we're the first user, clear the segment */
|
if (buf.shm_nattch == 1) { /* we're the first user, clear the segment */
|
||||||
memset(dmix->shmptr, 0, sizeof(snd_pcm_direct_share_t));
|
memset(dmix->shmptr, 0, sizeof(snd_pcm_direct_share_t));
|
||||||
|
if (dmix->ipc_gid >= 0) {
|
||||||
|
buf.shm_perm.gid = dmix->ipc_gid;
|
||||||
|
shmctl(dmix->shmid, IPC_SET, &buf);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ struct snd_pcm_direct {
|
||||||
snd_pcm_type_t type; /* type (dmix, dsnoop, dshare) */
|
snd_pcm_type_t type; /* type (dmix, dsnoop, dshare) */
|
||||||
key_t ipc_key; /* IPC key for semaphore and memory */
|
key_t ipc_key; /* IPC key for semaphore and memory */
|
||||||
mode_t ipc_perm; /* IPC socket permissions */
|
mode_t ipc_perm; /* IPC socket permissions */
|
||||||
|
int ipc_gid; /* IPC socket gid */
|
||||||
int semid; /* IPC global semaphore identification */
|
int semid; /* IPC global semaphore identification */
|
||||||
int shmid; /* IPC global shared memory identification */
|
int shmid; /* IPC global shared memory identification */
|
||||||
snd_pcm_direct_share_t *shmptr; /* pointer to shared memory area */
|
snd_pcm_direct_share_t *shmptr; /* pointer to shared memory area */
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <grp.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/shm.h>
|
#include <sys/shm.h>
|
||||||
|
|
@ -76,7 +77,7 @@ retryshm:
|
||||||
dmix->u.dmix.shmid_sum = shmget(dmix->ipc_key + 1, size,
|
dmix->u.dmix.shmid_sum = shmget(dmix->ipc_key + 1, size,
|
||||||
IPC_CREAT | dmix->ipc_perm);
|
IPC_CREAT | dmix->ipc_perm);
|
||||||
err = -errno;
|
err = -errno;
|
||||||
if (dmix->u.dmix.shmid_sum < 0){
|
if (dmix->u.dmix.shmid_sum < 0) {
|
||||||
if (errno == EINVAL)
|
if (errno == EINVAL)
|
||||||
if ((tmpid = shmget(dmix->ipc_key + 1, 0, dmix->ipc_perm)) != -1)
|
if ((tmpid = shmget(dmix->ipc_key + 1, 0, dmix->ipc_perm)) != -1)
|
||||||
if (!shmctl(tmpid, IPC_STAT, &buf))
|
if (!shmctl(tmpid, IPC_STAT, &buf))
|
||||||
|
|
@ -86,10 +87,20 @@ retryshm:
|
||||||
goto retryshm;
|
goto retryshm;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
if (!shmctl(dmix->u.dmix.shmid_sum, IPC_STAT, &buf)) {
|
||||||
|
err = -errno;
|
||||||
|
shm_sum_discard(dmix);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (dmix->ipc_gid >= 0) {
|
||||||
|
buf.shm_perm.gid = dmix->ipc_gid;
|
||||||
|
shmctl(dmix->u.dmix.shmid_sum, IPC_SET, &buf);
|
||||||
|
}
|
||||||
dmix->u.dmix.sum_buffer = shmat(dmix->u.dmix.shmid_sum, 0, 0);
|
dmix->u.dmix.sum_buffer = shmat(dmix->u.dmix.shmid_sum, 0, 0);
|
||||||
if (dmix->u.dmix.sum_buffer == (void *) -1) {
|
if (dmix->u.dmix.sum_buffer == (void *) -1) {
|
||||||
|
err = -errno;
|
||||||
shm_sum_discard(dmix);
|
shm_sum_discard(dmix);
|
||||||
return -errno;
|
return err;
|
||||||
}
|
}
|
||||||
mlock(dmix->u.dmix.sum_buffer, size);
|
mlock(dmix->u.dmix.sum_buffer, size);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -715,6 +726,7 @@ static snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = {
|
||||||
* \param name Name of PCM
|
* \param name Name of PCM
|
||||||
* \param ipc_key IPC key for semaphore and shared memory
|
* \param ipc_key IPC key for semaphore and shared memory
|
||||||
* \param ipc_perm IPC permissions for semaphore and shared memory
|
* \param ipc_perm IPC permissions for semaphore and shared memory
|
||||||
|
* \param ipc_gid IPC group ID for semaphore and shared memory
|
||||||
* \param params Parameters for slave
|
* \param params Parameters for slave
|
||||||
* \param bindings Channel bindings
|
* \param bindings Channel bindings
|
||||||
* \param slowptr Slow but more precise pointer updates
|
* \param slowptr Slow but more precise pointer updates
|
||||||
|
|
@ -728,7 +740,7 @@ static snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = {
|
||||||
* changed in future.
|
* changed in future.
|
||||||
*/
|
*/
|
||||||
int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
||||||
key_t ipc_key, mode_t ipc_perm,
|
key_t ipc_key, mode_t ipc_perm, int ipc_gid,
|
||||||
struct slave_params *params,
|
struct slave_params *params,
|
||||||
snd_config_t *bindings,
|
snd_config_t *bindings,
|
||||||
int slowptr,
|
int slowptr,
|
||||||
|
|
@ -759,6 +771,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
||||||
|
|
||||||
dmix->ipc_key = ipc_key;
|
dmix->ipc_key = ipc_key;
|
||||||
dmix->ipc_perm = ipc_perm;
|
dmix->ipc_perm = ipc_perm;
|
||||||
|
dmix->ipc_gid = ipc_gid;
|
||||||
dmix->semid = -1;
|
dmix->semid = -1;
|
||||||
dmix->shmid = -1;
|
dmix->shmid = -1;
|
||||||
|
|
||||||
|
|
@ -1050,6 +1063,7 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
||||||
int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
|
int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
|
||||||
key_t ipc_key = 0;
|
key_t ipc_key = 0;
|
||||||
mode_t ipc_perm = 0600;
|
mode_t ipc_perm = 0600;
|
||||||
|
int ipc_gid = -1;
|
||||||
int err;
|
int err;
|
||||||
snd_config_for_each(i, next, conf) {
|
snd_config_for_each(i, next, conf) {
|
||||||
snd_config_t *n = snd_config_iterator_entry(i);
|
snd_config_t *n = snd_config_iterator_entry(i);
|
||||||
|
|
@ -1063,6 +1077,7 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
||||||
err = snd_config_get_integer(n, &key);
|
err = snd_config_get_integer(n, &key);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
SNDERR("The field ipc_key must be an integer type");
|
SNDERR("The field ipc_key must be an integer type");
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
ipc_key = key;
|
ipc_key = key;
|
||||||
|
|
@ -1083,6 +1098,26 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
||||||
ipc_perm = strtol(perm, &endp, 8);
|
ipc_perm = strtol(perm, &endp, 8);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (strcmp(id, "ipc_gid") == 0) {
|
||||||
|
char *group;
|
||||||
|
char *endp;
|
||||||
|
err = snd_config_get_ascii(n, &group);
|
||||||
|
if (err < 0) {
|
||||||
|
SNDERR("The field ipc_gid must be a valid group");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (isdigit(*group) == 0) {
|
||||||
|
struct group *grp = getgrnam(group);
|
||||||
|
if (grp == NULL) {
|
||||||
|
SNDERR("The field ipc_gid must be a valid group (create group %s)", group);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ipc_gid = grp->gr_gid;
|
||||||
|
} else {
|
||||||
|
ipc_perm = strtol(group, &endp, 10);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (strcmp(id, "ipc_key_add_uid") == 0) {
|
if (strcmp(id, "ipc_key_add_uid") == 0) {
|
||||||
if ((err = snd_config_get_bool(n)) < 0) {
|
if ((err = snd_config_get_bool(n)) < 0) {
|
||||||
SNDERR("The field ipc_key_add_uid must be a boolean type");
|
SNDERR("The field ipc_key_add_uid must be a boolean type");
|
||||||
|
|
@ -1155,7 +1190,7 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
|
||||||
params.period_size = psize;
|
params.period_size = psize;
|
||||||
params.buffer_size = bsize;
|
params.buffer_size = bsize;
|
||||||
|
|
||||||
err = snd_pcm_dmix_open(pcmp, name, ipc_key, ipc_perm, ¶ms, bindings, slowptr, root, sconf, stream, mode);
|
err = snd_pcm_dmix_open(pcmp, name, ipc_key, ipc_perm, ipc_gid, ¶ms, bindings, slowptr, root, sconf, stream, mode);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
snd_config_delete(sconf);
|
snd_config_delete(sconf);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <grp.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/shm.h>
|
#include <sys/shm.h>
|
||||||
|
|
@ -578,6 +579,7 @@ static snd_pcm_fast_ops_t snd_pcm_dshare_fast_ops = {
|
||||||
* \param name Name of PCM
|
* \param name Name of PCM
|
||||||
* \param ipc_key IPC key for semaphore and shared memory
|
* \param ipc_key IPC key for semaphore and shared memory
|
||||||
* \param ipc_perm IPC permissions for semaphore and shared memory
|
* \param ipc_perm IPC permissions for semaphore and shared memory
|
||||||
|
* \param ipc_gid IPC group ID for semaphore and shared memory
|
||||||
* \param params Parameters for slave
|
* \param params Parameters for slave
|
||||||
* \param bindings Channel bindings
|
* \param bindings Channel bindings
|
||||||
* \param slowptr Slow but more precise pointer updates
|
* \param slowptr Slow but more precise pointer updates
|
||||||
|
|
@ -591,7 +593,7 @@ static snd_pcm_fast_ops_t snd_pcm_dshare_fast_ops = {
|
||||||
* changed in future.
|
* changed in future.
|
||||||
*/
|
*/
|
||||||
int snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
|
int snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
|
||||||
key_t ipc_key, mode_t ipc_perm,
|
key_t ipc_key, mode_t ipc_perm, int ipc_gid,
|
||||||
struct slave_params *params,
|
struct slave_params *params,
|
||||||
snd_config_t *bindings,
|
snd_config_t *bindings,
|
||||||
int slowptr,
|
int slowptr,
|
||||||
|
|
@ -629,6 +631,7 @@ int snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
|
||||||
|
|
||||||
dshare->ipc_key = ipc_key;
|
dshare->ipc_key = ipc_key;
|
||||||
dshare->ipc_perm = ipc_perm;
|
dshare->ipc_perm = ipc_perm;
|
||||||
|
dshare->ipc_gid = ipc_gid;
|
||||||
dshare->semid = -1;
|
dshare->semid = -1;
|
||||||
dshare->shmid = -1;
|
dshare->shmid = -1;
|
||||||
|
|
||||||
|
|
@ -851,6 +854,7 @@ int _snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
|
||||||
int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
|
int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
|
||||||
key_t ipc_key = 0;
|
key_t ipc_key = 0;
|
||||||
mode_t ipc_perm = 0600;
|
mode_t ipc_perm = 0600;
|
||||||
|
int ipc_gid = -1;
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
snd_config_for_each(i, next, conf) {
|
snd_config_for_each(i, next, conf) {
|
||||||
|
|
@ -885,6 +889,26 @@ int _snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
|
||||||
ipc_perm = strtol(perm, &endp, 8);
|
ipc_perm = strtol(perm, &endp, 8);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (strcmp(id, "ipc_gid") == 0) {
|
||||||
|
char *group;
|
||||||
|
char *endp;
|
||||||
|
err = snd_config_get_ascii(n, &group);
|
||||||
|
if (err < 0) {
|
||||||
|
SNDERR("The field ipc_gid must be a valid group");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (isdigit(*group) == 0) {
|
||||||
|
struct group *grp = getgrnam(group);
|
||||||
|
if (group == NULL) {
|
||||||
|
SNDERR("The field ipc_gid must be a valid group (create group %s)", grp);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ipc_gid = grp->gr_gid;
|
||||||
|
} else {
|
||||||
|
ipc_perm = strtol(group, &endp, 10);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (strcmp(id, "ipc_key_add_uid") == 0) {
|
if (strcmp(id, "ipc_key_add_uid") == 0) {
|
||||||
err = snd_config_get_bool(n);
|
err = snd_config_get_bool(n);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
|
@ -950,7 +974,7 @@ int _snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
|
||||||
|
|
||||||
params.period_size = psize;
|
params.period_size = psize;
|
||||||
params.buffer_size = bsize;
|
params.buffer_size = bsize;
|
||||||
err = snd_pcm_dshare_open(pcmp, name, ipc_key, ipc_perm, ¶ms, bindings, slowptr, root, sconf, stream, mode);
|
err = snd_pcm_dshare_open(pcmp, name, ipc_key, ipc_perm, ipc_gid, ¶ms, bindings, slowptr, root, sconf, stream, mode);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
snd_config_delete(sconf);
|
snd_config_delete(sconf);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <grp.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/shm.h>
|
#include <sys/shm.h>
|
||||||
|
|
@ -477,6 +478,7 @@ static snd_pcm_fast_ops_t snd_pcm_dsnoop_fast_ops = {
|
||||||
* \param name Name of PCM
|
* \param name Name of PCM
|
||||||
* \param ipc_key IPC key for semaphore and shared memory
|
* \param ipc_key IPC key for semaphore and shared memory
|
||||||
* \param ipc_perm IPC permissions for semaphore and shared memory
|
* \param ipc_perm IPC permissions for semaphore and shared memory
|
||||||
|
* \param ipc_gid IPC group ID for semaphore and shared memory
|
||||||
* \param params Parameters for slave
|
* \param params Parameters for slave
|
||||||
* \param bindings Channel bindings
|
* \param bindings Channel bindings
|
||||||
* \param slowptr Slow but more precise pointer updates
|
* \param slowptr Slow but more precise pointer updates
|
||||||
|
|
@ -490,7 +492,7 @@ static snd_pcm_fast_ops_t snd_pcm_dsnoop_fast_ops = {
|
||||||
* changed in future.
|
* changed in future.
|
||||||
*/
|
*/
|
||||||
int snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
|
int snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
|
||||||
key_t ipc_key, mode_t ipc_perm,
|
key_t ipc_key, mode_t ipc_perm, int ipc_gid,
|
||||||
struct slave_params *params,
|
struct slave_params *params,
|
||||||
snd_config_t *bindings,
|
snd_config_t *bindings,
|
||||||
int slowptr,
|
int slowptr,
|
||||||
|
|
@ -520,6 +522,7 @@ int snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
|
||||||
|
|
||||||
dsnoop->ipc_key = ipc_key;
|
dsnoop->ipc_key = ipc_key;
|
||||||
dsnoop->ipc_perm = ipc_perm;
|
dsnoop->ipc_perm = ipc_perm;
|
||||||
|
dsnoop->ipc_gid = ipc_gid;
|
||||||
dsnoop->semid = -1;
|
dsnoop->semid = -1;
|
||||||
dsnoop->shmid = -1;
|
dsnoop->shmid = -1;
|
||||||
|
|
||||||
|
|
@ -732,6 +735,7 @@ int _snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
|
||||||
int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
|
int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
|
||||||
key_t ipc_key = 0;
|
key_t ipc_key = 0;
|
||||||
mode_t ipc_perm = 0600;
|
mode_t ipc_perm = 0600;
|
||||||
|
int ipc_gid = -1;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
snd_config_for_each(i, next, conf) {
|
snd_config_for_each(i, next, conf) {
|
||||||
|
|
@ -766,6 +770,26 @@ int _snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
|
||||||
ipc_perm = strtol(perm, &endp, 8);
|
ipc_perm = strtol(perm, &endp, 8);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (strcmp(id, "ipc_gid") == 0) {
|
||||||
|
char *group;
|
||||||
|
char *endp;
|
||||||
|
err = snd_config_get_ascii(n, &group);
|
||||||
|
if (err < 0) {
|
||||||
|
SNDERR("The field ipc_gid must be a valid group");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (isdigit(*group) == 0) {
|
||||||
|
struct group *grp = getgrnam(group);
|
||||||
|
if (group == NULL) {
|
||||||
|
SNDERR("The field ipc_gid must be a valid group (create group %s)", grp);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ipc_gid = grp->gr_gid;
|
||||||
|
} else {
|
||||||
|
ipc_perm = strtol(group, &endp, 10);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (strcmp(id, "ipc_key_add_uid") == 0) {
|
if (strcmp(id, "ipc_key_add_uid") == 0) {
|
||||||
err = snd_config_get_bool(n);
|
err = snd_config_get_bool(n);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
|
@ -831,7 +855,7 @@ int _snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
|
||||||
|
|
||||||
params.period_size = psize;
|
params.period_size = psize;
|
||||||
params.buffer_size = bsize;
|
params.buffer_size = bsize;
|
||||||
err = snd_pcm_dsnoop_open(pcmp, name, ipc_key, ipc_perm, ¶ms, bindings, slowptr, root, sconf, stream, mode);
|
err = snd_pcm_dsnoop_open(pcmp, name, ipc_key, ipc_perm, ipc_gid, ¶ms, bindings, slowptr, root, sconf, stream, mode);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
snd_config_delete(sconf);
|
snd_config_delete(sconf);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue