vserver 1.9.5.x5
[linux-2.6.git] / sound / core / ioctl32 / pcm32.c
index caf5ee4..1e37cda 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/compat.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
+#include <sound/minors.h>
 #include "ioctl32.h"
 
 
@@ -41,23 +42,15 @@ struct sndrv_pcm_uframes_str32 {
        u32 val;
 };
 
-#define CVT_sndrv_pcm_sframes_str() { COPY(val); }
-#define CVT_sndrv_pcm_uframes_str() { COPY(val); }
+#define CVT_sndrv_pcm_sframes_str() { COPY_CVT(val); }
+#define CVT_sndrv_pcm_uframes_str() { COPY_CVT(val); }
 
 
-struct sndrv_interval32 {
-       u32 min, max;
-       unsigned int openmin:1,
-                    openmax:1,
-                    integer:1,
-                    empty:1;
-};
-
 struct sndrv_pcm_hw_params32 {
        u32 flags;
        struct sndrv_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
        struct sndrv_mask mres[5];      /* reserved masks */
-       struct sndrv_interval32 intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
+       struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
        struct sndrv_interval ires[9];  /* reserved intervals */
        u32 rmask;
        u32 cmask;
@@ -69,31 +62,6 @@ struct sndrv_pcm_hw_params32 {
        unsigned char reserved[64];
 } __attribute__((packed));
 
-#define numberof(array) ARRAY_SIZE(array)
-
-#define CVT_sndrv_pcm_hw_params()\
-{\
-       unsigned int i;\
-       COPY(flags);\
-       for (i = 0; i < numberof(dst->masks); i++)\
-               COPY(masks[i]);\
-       for (i = 0; i < numberof(dst->intervals); i++) {\
-               COPY(intervals[i].min);\
-               COPY(intervals[i].max);\
-               COPY(intervals[i].openmin);\
-               COPY(intervals[i].openmax);\
-               COPY(intervals[i].integer);\
-               COPY(intervals[i].empty);\
-       }\
-       COPY(rmask);\
-       COPY(cmask);\
-       COPY(info);\
-       COPY(msbits);\
-       COPY(rate_num);\
-       COPY(rate_den);\
-       COPY(fifo_size);\
-}
-
 struct sndrv_pcm_sw_params32 {
        s32 tstamp_mode;
        u32 period_step;
@@ -113,13 +81,13 @@ struct sndrv_pcm_sw_params32 {
        COPY(tstamp_mode);\
        COPY(period_step);\
        COPY(sleep_min);\
-       COPY(avail_min);\
-       COPY(xfer_align);\
-       COPY(start_threshold);\
-       COPY(stop_threshold);\
-       COPY(silence_threshold);\
-       COPY(silence_size);\
-       COPY(boundary);\
+       COPY_CVT(avail_min);\
+       COPY_CVT(xfer_align);\
+       COPY_CVT(start_threshold);\
+       COPY_CVT(stop_threshold);\
+       COPY_CVT(silence_threshold);\
+       COPY_CVT(silence_size);\
+       COPY_CVT(boundary);\
 }
 
 struct sndrv_pcm_channel_info32 {
@@ -132,7 +100,7 @@ struct sndrv_pcm_channel_info32 {
 #define CVT_sndrv_pcm_channel_info()\
 {\
        COPY(channel);\
-       COPY(offset);\
+       COPY_CVT(offset);\
        COPY(first);\
        COPY(step);\
 }
@@ -154,26 +122,96 @@ struct sndrv_pcm_status32 {
 #define CVT_sndrv_pcm_status()\
 {\
        COPY(state);\
-       COPY(trigger_tstamp.tv_sec);\
-       COPY(trigger_tstamp.tv_nsec);\
-       COPY(tstamp.tv_sec);\
-       COPY(tstamp.tv_nsec);\
-       COPY(appl_ptr);\
-       COPY(hw_ptr);\
-       COPY(delay);\
-       COPY(avail);\
-       COPY(avail_max);\
-       COPY(overrange);\
+       COPY_CVT(trigger_tstamp.tv_sec);\
+       COPY_CVT(trigger_tstamp.tv_nsec);\
+       COPY_CVT(tstamp.tv_sec);\
+       COPY_CVT(tstamp.tv_nsec);\
+       COPY_CVT(appl_ptr);\
+       COPY_CVT(hw_ptr);\
+       COPY_CVT(delay);\
+       COPY_CVT(avail);\
+       COPY_CVT(avail_max);\
+       COPY_CVT(overrange);\
        COPY(suspended_state);\
 }
 
 DEFINE_ALSA_IOCTL(pcm_uframes_str);
 DEFINE_ALSA_IOCTL(pcm_sframes_str);
-DEFINE_ALSA_IOCTL_BIG(pcm_hw_params);
 DEFINE_ALSA_IOCTL(pcm_sw_params);
 DEFINE_ALSA_IOCTL(pcm_channel_info);
 DEFINE_ALSA_IOCTL(pcm_status);
 
+/* sanity device check */
+extern int snd_major;
+static int sanity_check_pcm(struct file *file)
+{
+       unsigned short minor;
+       if (imajor(file->f_dentry->d_inode) != snd_major)
+               return -ENOTTY;
+       minor = iminor(file->f_dentry->d_inode);
+       if (minor >= 256 || 
+           minor % SNDRV_MINOR_DEVICES < SNDRV_MINOR_PCM_PLAYBACK)
+               return -ENOTTY;
+       return 0;
+}
+
+/* recalcuate the boundary within 32bit */
+static void recalculate_boundary(snd_pcm_runtime_t *runtime)
+{
+       if (! runtime->buffer_size)
+               return;
+       runtime->boundary = runtime->buffer_size;
+       while (runtime->boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
+               runtime->boundary *= 2;
+}
+
+/* both for HW_PARAMS and HW_REFINE */
+static int _snd_ioctl32_pcm_hw_params(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *file, unsigned int native_ctl)
+{
+       struct sndrv_pcm_hw_params32 __user *data32;
+       struct sndrv_pcm_hw_params *data;
+       snd_pcm_file_t *pcm_file;
+       snd_pcm_substream_t *substream;
+       snd_pcm_runtime_t *runtime;
+       int err;
+
+       if (sanity_check_pcm(file))
+               return -ENOTTY;
+       if (! (pcm_file = file->private_data))
+               return -ENOTTY;
+       if (! (substream = pcm_file->substream))
+               return -ENOTTY;
+       if (! (runtime = substream->runtime))
+               return -ENOTTY;
+
+       data32 = compat_ptr(arg);
+       data = kmalloc(sizeof(*data), GFP_KERNEL);
+       if (data == NULL)
+               return -ENOMEM;
+       if (copy_from_user(data, data32, sizeof(*data32))) {
+               err = -EFAULT;
+               goto error;
+       }
+       if (native_ctl == SNDRV_PCM_IOCTL_HW_REFINE)
+               err = snd_pcm_hw_refine(substream, data);
+       else
+               err = snd_pcm_hw_params(substream, data);
+       if (err < 0)
+               goto error;
+       if (copy_to_user(data32, data, sizeof(*data32)) ||
+           __put_user((u32)data->fifo_size, &data32->fifo_size)) {
+               err = -EFAULT;
+               goto error;
+       }
+
+       if (native_ctl == SNDRV_PCM_IOCTL_HW_PARAMS)
+               recalculate_boundary(runtime);
+ error:
+       kfree(data);
+       return err;
+}
+
+
 /*
  */
 struct sndrv_xferi32 {
@@ -185,24 +223,24 @@ struct sndrv_xferi32 {
 static int _snd_ioctl32_xferi(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *file, unsigned int native_ctl)
 {
        struct sndrv_xferi32 data32;
-       struct sndrv_xferi data;
-       mm_segment_t oldseg;
+       struct sndrv_xferi __user *data;
+       snd_pcm_sframes_t result;
        int err;
 
        if (copy_from_user(&data32, (void __user *)arg, sizeof(data32)))
                return -EFAULT;
-       memset(&data, 0, sizeof(data));
-       data.result = data32.result;
-       data.buf = compat_ptr(data32.buf);
-       data.frames = data32.frames;
-       oldseg = get_fs();
-       set_fs(KERNEL_DS);
-       err = file->f_op->ioctl(file->f_dentry->d_inode, file, native_ctl, (unsigned long)&data);
-       set_fs(oldseg);
+       data = compat_alloc_user_space(sizeof(*data));
+       if (put_user((snd_pcm_sframes_t)data32.result, &data->result) ||
+           __put_user(compat_ptr(data32.buf), &data->buf) ||
+           __put_user((snd_pcm_uframes_t)data32.frames, &data->frames))
+               return -EFAULT;
+       err = file->f_op->ioctl(file->f_dentry->d_inode, file, native_ctl, (unsigned long)data);
        if (err < 0)
                return err;
        /* copy the result */
-       data32.result = data.result;
+       if (__get_user(result, &data->result))
+               return -EFAULT;
+       data32.result = result;
        if (copy_to_user((void __user *)arg, &data32, sizeof(data32)))
                return -EFAULT;
        return 0;
@@ -226,18 +264,20 @@ static int _snd_ioctl32_xfern(unsigned int fd, unsigned int cmd, unsigned long a
 {
        snd_pcm_file_t *pcm_file;
        snd_pcm_substream_t *substream;
+       struct sndrv_xfern32 __user *srcptr = compat_ptr(arg);
        struct sndrv_xfern32 data32;
-       struct sndrv_xfern32 __user *srcptr = (void __user *)arg;
-       void __user **bufs = NULL;
+       void __user **bufs;
        int err = 0, ch, i;
        u32 __user *bufptr;
-       mm_segment_t oldseg;
 
-       /* FIXME: need to check whether fop->ioctl is sane */
-
-       pcm_file = file->private_data;
-       substream = pcm_file->substream;
-       snd_assert(substream != NULL && substream->runtime, return -ENXIO);
+       if (sanity_check_pcm(file))
+               return -ENOTTY;
+       if (! (pcm_file = file->private_data))
+               return -ENOTTY;
+       if (! (substream = pcm_file->substream))
+               return -ENOTTY;
+       if (! substream->runtime)
+               return -ENOTTY;
 
        /* check validty of the command */
        switch (native_ctl) {
@@ -254,22 +294,21 @@ static int _snd_ioctl32_xfern(unsigned int fd, unsigned int cmd, unsigned long a
        }
        if ((ch = substream->runtime->channels) > 128)
                return -EINVAL;
-       if (get_user(data32.frames, &srcptr->frames))
+       if (copy_from_user(&data32, (void __user *)arg, sizeof(data32)))
                return -EFAULT;
-       __get_user(data32.bufs, &srcptr->bufs);
        bufptr = compat_ptr(data32.bufs);
-       bufs = kmalloc(sizeof(void *) * 128, GFP_KERNEL);
+       bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL);
        if (bufs == NULL)
                return -ENOMEM;
        for (i = 0; i < ch; i++) {
                u32 ptr;
-               if (get_user(ptr, bufptr))
+               if (get_user(ptr, bufptr)) {
+                       kfree(bufs);
                        return -EFAULT;
+               }
                bufs[ch] = compat_ptr(ptr);
                bufptr++;
        }
-       oldseg = get_fs();
-       set_fs(KERNEL_DS);
        switch (native_ctl) {
        case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
                err = snd_pcm_lib_writev(substream, bufs, data32.frames);
@@ -278,107 +317,15 @@ static int _snd_ioctl32_xfern(unsigned int fd, unsigned int cmd, unsigned long a
                err = snd_pcm_lib_readv(substream, bufs, data32.frames);
                break;
        }
-       set_fs(oldseg);
        if (err >= 0) {
                if (put_user(err, &srcptr->result))
                        err = -EFAULT;
        }
        kfree(bufs);
-       return 0;
-}
-
-
-struct sndrv_pcm_hw_params_old32 {
-       u32 flags;
-       u32 masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
-                          SNDRV_PCM_HW_PARAM_ACCESS + 1];
-       struct sndrv_interval32 intervals[SNDRV_PCM_HW_PARAM_TICK_TIME -
-                                       SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1];
-       u32 rmask;
-       u32 cmask;
-       u32 info;
-       u32 msbits;
-       u32 rate_num;
-       u32 rate_den;
-       u32 fifo_size;
-       unsigned char reserved[64];
-} __attribute__((packed));
-
-#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5))
-#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5))
-
-static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params, struct sndrv_pcm_hw_params_old32 *oparams)
-{
-       unsigned int i;
-
-       memset(params, 0, sizeof(*params));
-       params->flags = oparams->flags;
-       for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
-               params->masks[i].bits[0] = oparams->masks[i];
-       memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals));
-       params->rmask = __OLD_TO_NEW_MASK(oparams->rmask);
-       params->cmask = __OLD_TO_NEW_MASK(oparams->cmask);
-       params->info = oparams->info;
-       params->msbits = oparams->msbits;
-       params->rate_num = oparams->rate_num;
-       params->rate_den = oparams->rate_den;
-       params->fifo_size = oparams->fifo_size;
-}
-
-static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old32 *oparams, snd_pcm_hw_params_t *params)
-{
-       unsigned int i;
-
-       memset(oparams, 0, sizeof(*oparams));
-       oparams->flags = params->flags;
-       for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
-               oparams->masks[i] = params->masks[i].bits[0];
-       memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals));
-       oparams->rmask = __NEW_TO_OLD_MASK(params->rmask);
-       oparams->cmask = __NEW_TO_OLD_MASK(params->cmask);
-       oparams->info = params->info;
-       oparams->msbits = params->msbits;
-       oparams->rate_num = params->rate_num;
-       oparams->rate_den = params->rate_den;
-       oparams->fifo_size = params->fifo_size;
-}
-
-static int _snd_ioctl32_pcm_hw_params_old(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *file, unsigned int native_ctl)
-{
-       struct sndrv_pcm_hw_params_old32 *data32;
-       struct sndrv_pcm_hw_params *data;
-       mm_segment_t oldseg;
-       int err;
-
-       data32 = kcalloc(1, sizeof(*data32), GFP_KERNEL);
-       data = kcalloc(1, sizeof(*data), GFP_KERNEL);
-       if (data32 == NULL || data == NULL) {
-               err = -ENOMEM;
-               goto __end;
-       }
-       if (copy_from_user(data32, (void __user *)arg, sizeof(*data32))) {
-               err = -EFAULT;
-               goto __end;
-       }
-       snd_pcm_hw_convert_from_old_params(data, data32);
-       oldseg = get_fs();
-       set_fs(KERNEL_DS);
-       err = file->f_op->ioctl(file->f_dentry->d_inode, file, native_ctl, (unsigned long)data);
-       set_fs(oldseg);
-       if (err < 0)
-               goto __end;
-       snd_pcm_hw_convert_to_old_params(data32, data);
-       err = 0;
-       if (copy_to_user((void __user *)arg, data32, sizeof(*data32)))
-               err = -EFAULT;
-      __end:
-       if (data)
-               kfree(data);
-       if (data32)
-               kfree(data32);
        return err;
 }
 
+
 struct sndrv_pcm_mmap_status32 {
        s32 state;
        s32 pad1;
@@ -409,15 +356,15 @@ struct sndrv_pcm_sync_ptr32 {
        COPY(flags);\
        COPY(s.status.state);\
        COPY(s.status.pad1);\
-       COPY(s.status.hw_ptr);\
-       COPY(s.status.tstamp.tv_sec);\
-       COPY(s.status.tstamp.tv_nsec);\
+       COPY_CVT(s.status.hw_ptr);\
+       COPY_CVT(s.status.tstamp.tv_sec);\
+       COPY_CVT(s.status.tstamp.tv_nsec);\
        COPY(s.status.suspended_state);\
-       COPY(c.control.appl_ptr);\
-       COPY(c.control.avail_min);\
+       COPY_CVT(c.control.appl_ptr);\
+       COPY_CVT(c.control.avail_min);\
 }
 
-DEFINE_ALSA_IOCTL_BIG(pcm_sync_ptr);
+DEFINE_ALSA_IOCTL(pcm_sync_ptr);
 
 /*
  */
@@ -425,12 +372,11 @@ DEFINE_ALSA_IOCTL_BIG(pcm_sync_ptr);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_hw_refine, pcm_hw_params, SNDRV_PCM_IOCTL_HW_REFINE);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_hw_params, pcm_hw_params, SNDRV_PCM_IOCTL_HW_PARAMS);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_sw_params, pcm_sw_params, SNDRV_PCM_IOCTL_SW_PARAMS);
-DEFINE_ALSA_IOCTL_ENTRY(pcm_hw_refine_old, pcm_hw_params_old, SNDRV_PCM_IOCTL_HW_REFINE);
-DEFINE_ALSA_IOCTL_ENTRY(pcm_hw_params_old, pcm_hw_params_old, SNDRV_PCM_IOCTL_HW_PARAMS);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_status, pcm_status, SNDRV_PCM_IOCTL_STATUS);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_delay, pcm_sframes_str, SNDRV_PCM_IOCTL_DELAY);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_channel_info, pcm_channel_info, SNDRV_PCM_IOCTL_CHANNEL_INFO);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_rewind, pcm_uframes_str, SNDRV_PCM_IOCTL_REWIND);
+DEFINE_ALSA_IOCTL_ENTRY(pcm_forward, pcm_uframes_str, SNDRV_PCM_IOCTL_FORWARD);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_readi, xferi, SNDRV_PCM_IOCTL_READI_FRAMES);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_writei, xferi, SNDRV_PCM_IOCTL_WRITEI_FRAMES);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_readn, xfern, SNDRV_PCM_IOCTL_READN_FRAMES);
@@ -438,6 +384,30 @@ DEFINE_ALSA_IOCTL_ENTRY(pcm_writen, xfern, SNDRV_PCM_IOCTL_WRITEN_FRAMES);
 DEFINE_ALSA_IOCTL_ENTRY(pcm_sync_ptr, pcm_sync_ptr, SNDRV_PCM_IOCTL_SYNC_PTR);
 
 
+/*
+ * When PCM is used on 32bit mode, we need to disable
+ * mmap of PCM status/control records because of the size
+ * incompatibility.
+ * 
+ * Since INFO ioctl is always called at first, we mark the
+ * mmap-disabling in this ioctl wrapper.
+ */
+static int snd_pcm_info_ioctl32(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *filp)
+{
+       snd_pcm_file_t *pcm_file;
+       snd_pcm_substream_t *substream;
+       if (! filp->f_op || ! filp->f_op->ioctl)
+               return -ENOTTY;
+       pcm_file = filp->private_data;
+       if (! pcm_file)
+               return -ENOTTY;
+       substream = pcm_file->substream;
+       if (! substream)
+               return -ENOTTY;
+       substream->no_mmap_ctrl = 1;
+       return filp->f_op->ioctl(filp->f_dentry->d_inode, filp, cmd, arg);
+}
+
 /*
  */
 #define AP(x) snd_ioctl32_##x
@@ -450,24 +420,22 @@ enum {
        SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
        SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct sndrv_pcm_channel_info32),
        SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
+       SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32),
        SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct sndrv_xferi32),
        SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct sndrv_xferi32),
        SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct sndrv_xfern32),
        SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct sndrv_xfern32),
-       SNDRV_PCM_IOCTL_HW_REFINE_OLD32 = _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old32),
-       SNDRV_PCM_IOCTL_HW_PARAMS_OLD32 = _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old32),
-       SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr),
+       SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr32),
 
 };
 
 struct ioctl32_mapper pcm_mappers[] = {
        MAP_COMPAT(SNDRV_PCM_IOCTL_PVERSION),
-       MAP_COMPAT(SNDRV_PCM_IOCTL_INFO),
+       /* MAP_COMPAT(SNDRV_PCM_IOCTL_INFO), */
+       { SNDRV_PCM_IOCTL_INFO, snd_pcm_info_ioctl32 },
        MAP_COMPAT(SNDRV_PCM_IOCTL_TSTAMP),
        { SNDRV_PCM_IOCTL_HW_REFINE32, AP(pcm_hw_refine) },
        { SNDRV_PCM_IOCTL_HW_PARAMS32, AP(pcm_hw_params) },
-       { SNDRV_PCM_IOCTL_HW_REFINE_OLD32, AP(pcm_hw_refine_old) },
-       { SNDRV_PCM_IOCTL_HW_PARAMS_OLD32, AP(pcm_hw_params_old) },
        MAP_COMPAT(SNDRV_PCM_IOCTL_HW_FREE),
        { SNDRV_PCM_IOCTL_SW_PARAMS32, AP(pcm_sw_params) },
        { SNDRV_PCM_IOCTL_STATUS32, AP(pcm_status) },
@@ -484,6 +452,7 @@ struct ioctl32_mapper pcm_mappers[] = {
        { SNDRV_PCM_IOCTL_REWIND32, AP(pcm_rewind) },
        MAP_COMPAT(SNDRV_PCM_IOCTL_RESUME),
        MAP_COMPAT(SNDRV_PCM_IOCTL_XRUN),
+       { SNDRV_PCM_IOCTL_FORWARD32, AP(pcm_forward) },
        { SNDRV_PCM_IOCTL_WRITEI_FRAMES32, AP(pcm_writei) },
        { SNDRV_PCM_IOCTL_READI_FRAMES32, AP(pcm_readi) },
        { SNDRV_PCM_IOCTL_WRITEN_FRAMES32, AP(pcm_writen) },