summaryrefslogtreecommitdiff
path: root/backend/gt68xx_shm_channel.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/gt68xx_shm_channel.c')
-rw-r--r--backend/gt68xx_shm_channel.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/backend/gt68xx_shm_channel.c b/backend/gt68xx_shm_channel.c
new file mode 100644
index 0000000..bca7425
--- /dev/null
+++ b/backend/gt68xx_shm_channel.c
@@ -0,0 +1,673 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2002 Sergey Vlasov <vsu@altlinux.ru>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/** @file
+ * @brief Shared memory channel implementation.
+ */
+
+#include "gt68xx_shm_channel.h"
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifndef SHM_R
+#define SHM_R 0
+#endif
+
+#ifndef SHM_W
+#define SHM_W 0
+#endif
+
+/** Shared memory channel.
+ *
+ */
+struct Shm_Channel
+{
+ SANE_Int buf_size; /**< Size of each buffer */
+ SANE_Int buf_count; /**< Number of buffers */
+ void *shm_area; /**< Address of shared memory area */
+ SANE_Byte **buffers; /**< Array of pointers to buffers */
+ SANE_Int *buffer_bytes; /**< Array of buffer byte counts */
+ int writer_put_pipe[2]; /**< Notification pipe from writer */
+ int reader_put_pipe[2]; /**< Notification pipe from reader */
+};
+
+/** Dummy union to find out the needed alignment */
+union Shm_Channel_Align
+{
+ int i;
+ long l;
+ void *ptr;
+ void (*func_ptr) (void);
+ double d;
+};
+
+/** Check if shm_channel is valid */
+#define SHM_CHANNEL_CHECK(shm_channel, func_name) \
+ do { \
+ if ((shm_channel) == NULL) \
+ { \
+ DBG (3, "%s: BUG: shm_channel==NULL\n", (func_name)); \
+ return SANE_STATUS_INVAL; \
+ } \
+ } while (SANE_FALSE)
+
+/** Alignment for shared memory contents */
+#define SHM_CHANNEL_ALIGNMENT (sizeof (union Shm_Channel_Align))
+
+/** Align the given size up to a multiple of the given alignment */
+#define SHM_CHANNEL_ROUND_UP(size, align) \
+ ( ((size) % (align)) ? ((size)/(align) + 1)*(align) : (size) )
+
+/** Align the size using SHM_CHANNEL_ALIGNMENT */
+#define SHM_CHANNEL_ALIGN(size) \
+ SHM_CHANNEL_ROUND_UP((size_t) (size), SHM_CHANNEL_ALIGNMENT)
+
+/** Close a file descriptor if it is currently open.
+ *
+ * This function checks if the file descriptor is not -1, and sets it to -1
+ * after close (so that it will not be closed twice).
+ *
+ * @param fd_var Pointer to a variable holding the file descriptor.
+ */
+static void
+shm_channel_fd_safe_close (int *fd_var)
+{
+ if (*fd_var != -1)
+ {
+ close (*fd_var);
+ *fd_var = -1;
+ }
+}
+
+static SANE_Status
+shm_channel_fd_set_close_on_exec (int fd)
+{
+ long value;
+
+ value = fcntl (fd, F_GETFD, 0L);
+ if (value == -1)
+ return SANE_STATUS_IO_ERROR;
+ if (fcntl (fd, F_SETFD, value | FD_CLOEXEC) == -1)
+ return SANE_STATUS_IO_ERROR;
+
+ return SANE_STATUS_GOOD;
+}
+
+#if 0
+static SANE_Status
+shm_channel_fd_set_non_blocking (int fd, SANE_Bool non_blocking)
+{
+ long value;
+
+ value = fcntl (fd, F_GETFL, 0L);
+ if (value == -1)
+ return SANE_STATUS_IO_ERROR;
+
+ if (non_blocking)
+ value |= O_NONBLOCK;
+ else
+ value &= ~O_NONBLOCK;
+
+ if (fcntl (fd, F_SETFL, value) == -1)
+ return SANE_STATUS_IO_ERROR;
+
+ return SANE_STATUS_GOOD;
+}
+#endif
+
+/** Create a new shared memory channel.
+ *
+ * This function should be called before the fork to set up the shared memory.
+ *
+ * @param buf_size Size of each shared memory buffer in bytes.
+ * @param buf_count Number of shared memory buffers (up to 255).
+ * @param shm_channel_return Returned shared memory channel object.
+ */
+SANE_Status
+shm_channel_new (SANE_Int buf_size,
+ SANE_Int buf_count, Shm_Channel ** shm_channel_return)
+{
+ Shm_Channel *shm_channel;
+ void *shm_area;
+ SANE_Byte *shm_data;
+ int shm_buffer_bytes_size, shm_buffer_size;
+ int shm_size;
+ int shm_id;
+ int i;
+
+ if (buf_size <= 0)
+ {
+ DBG (3, "shm_channel_new: invalid buf_size=%d\n", buf_size);
+ return SANE_STATUS_INVAL;
+ }
+ if (buf_count <= 0 || buf_count > 255)
+ {
+ DBG (3, "shm_channel_new: invalid buf_count=%d\n", buf_count);
+ return SANE_STATUS_INVAL;
+ }
+ if (!shm_channel_return)
+ {
+ DBG (3, "shm_channel_new: BUG: shm_channel_return==NULL\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ *shm_channel_return = NULL;
+
+ shm_channel = (Shm_Channel *) malloc (sizeof (Shm_Channel));
+ if (!shm_channel)
+ {
+ DBG (3, "shm_channel_new: no memory for Shm_Channel\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ shm_channel->buf_size = buf_size;
+ shm_channel->buf_count = buf_count;
+ shm_channel->shm_area = NULL;
+ shm_channel->buffers = NULL;
+ shm_channel->buffer_bytes = NULL;
+ shm_channel->writer_put_pipe[0] = shm_channel->writer_put_pipe[1] = -1;
+ shm_channel->reader_put_pipe[0] = shm_channel->reader_put_pipe[1] = -1;
+
+ shm_channel->buffers =
+ (SANE_Byte **) malloc (sizeof (SANE_Byte *) * buf_count);
+ if (!shm_channel->buffers)
+ {
+ DBG (3, "shm_channel_new: no memory for buffer pointers\n");
+ shm_channel_free (shm_channel);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ if (pipe (shm_channel->writer_put_pipe) == -1)
+ {
+ DBG (3, "shm_channel_new: cannot create writer put pipe: %s\n",
+ strerror (errno));
+ shm_channel_free (shm_channel);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ if (pipe (shm_channel->reader_put_pipe) == -1)
+ {
+ DBG (3, "shm_channel_new: cannot create reader put pipe: %s\n",
+ strerror (errno));
+ shm_channel_free (shm_channel);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ shm_channel_fd_set_close_on_exec (shm_channel->reader_put_pipe[0]);
+ shm_channel_fd_set_close_on_exec (shm_channel->reader_put_pipe[1]);
+ shm_channel_fd_set_close_on_exec (shm_channel->writer_put_pipe[0]);
+ shm_channel_fd_set_close_on_exec (shm_channel->writer_put_pipe[1]);
+
+ shm_buffer_bytes_size = SHM_CHANNEL_ALIGN (sizeof (SANE_Int) * buf_count);
+ shm_buffer_size = SHM_CHANNEL_ALIGN (buf_size);
+ shm_size = shm_buffer_bytes_size + buf_count * shm_buffer_size;
+
+ shm_id = shmget (IPC_PRIVATE, shm_size, IPC_CREAT | SHM_R | SHM_W);
+ if (shm_id == -1)
+ {
+ DBG (3, "shm_channel_new: cannot create shared memory segment: %s\n",
+ strerror (errno));
+ shm_channel_free (shm_channel);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ shm_area = shmat (shm_id, NULL, 0);
+ if (shm_area == (void *) -1)
+ {
+ DBG (3, "shm_channel_new: cannot attach to shared memory segment: %s\n",
+ strerror (errno));
+ shmctl (shm_id, IPC_RMID, NULL);
+ shm_channel_free (shm_channel);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ if (shmctl (shm_id, IPC_RMID, NULL) == -1)
+ {
+ DBG (3, "shm_channel_new: cannot remove shared memory segment id: %s\n",
+ strerror (errno));
+ shmdt (shm_area);
+ shmctl (shm_id, IPC_RMID, NULL);
+ shm_channel_free (shm_channel);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ shm_channel->shm_area = shm_area;
+
+ shm_channel->buffer_bytes = (SANE_Int *) shm_area;
+ shm_data = ((SANE_Byte *) shm_area) + shm_buffer_bytes_size;
+ for (i = 0; i < shm_channel->buf_count; ++i)
+ {
+ shm_channel->buffers[i] = shm_data;
+ shm_data += shm_buffer_size;
+ }
+
+ *shm_channel_return = shm_channel;
+ return SANE_STATUS_GOOD;
+}
+
+/** Close the shared memory channel and release associated resources.
+ *
+ * @param shm_channel Shared memory channel object.
+ */
+SANE_Status
+shm_channel_free (Shm_Channel * shm_channel)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_free");
+
+ if (shm_channel->shm_area)
+ {
+ shmdt (shm_channel->shm_area);
+ shm_channel->shm_area = NULL;
+ }
+
+ if (shm_channel->buffers)
+ {
+ free (shm_channel->buffers);
+ shm_channel->buffers = NULL;
+ }
+
+ shm_channel_fd_safe_close (&shm_channel->reader_put_pipe[0]);
+ shm_channel_fd_safe_close (&shm_channel->reader_put_pipe[1]);
+ shm_channel_fd_safe_close (&shm_channel->writer_put_pipe[0]);
+ shm_channel_fd_safe_close (&shm_channel->writer_put_pipe[1]);
+
+ return SANE_STATUS_GOOD;
+}
+
+/** Initialize the shared memory channel in the writer process.
+ *
+ * This function should be called after the fork in the process which will
+ * write data to the channel.
+ *
+ * @param shm_channel Shared memory channel object.
+ */
+SANE_Status
+shm_channel_writer_init (Shm_Channel * shm_channel)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_writer_init");
+
+ shm_channel_fd_safe_close (&shm_channel->writer_put_pipe[0]);
+ shm_channel_fd_safe_close (&shm_channel->reader_put_pipe[1]);
+
+ return SANE_STATUS_GOOD;
+}
+
+/** Get a free shared memory buffer for writing.
+ *
+ * This function may block waiting for a free buffer (if the reader process
+ * does not process the data fast enough).
+ *
+ * After successfull call to this function the writer process should fill the
+ * buffer with the data and pass the buffer identifier from @a buffer_id_return
+ * to shm_channel_writer_put_buffer() to give the buffer to the reader process.
+ *
+ * @param shm_channel Shared memory channel object.
+ * @param buffer_id_return Returned buffer identifier.
+ * @param buffer_addr_return Returned buffer address.
+ *
+ * @return
+ * - SANE_STATUS_GOOD - a free buffer was available (or became available after
+ * waiting for it); @a buffer_id_return and @a buffer_addr_return are filled
+ * with valid values.
+ * - SANE_STATUS_EOF - the reader process has closed its half of the channel.
+ * - SANE_STATUS_IO_ERROR - an I/O error occured.
+ */
+SANE_Status
+shm_channel_writer_get_buffer (Shm_Channel * shm_channel,
+ SANE_Int * buffer_id_return,
+ SANE_Byte ** buffer_addr_return)
+{
+ SANE_Byte buf_index;
+ int bytes_read;
+
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_writer_get_buffer");
+
+ do
+ bytes_read = read (shm_channel->reader_put_pipe[0], &buf_index, 1);
+ while (bytes_read == -1 && errno == EINTR);
+
+ if (bytes_read == 1)
+ {
+ SANE_Int index = buf_index;
+ if (index < shm_channel->buf_count)
+ {
+ *buffer_id_return = index;
+ *buffer_addr_return = shm_channel->buffers[index];
+ return SANE_STATUS_GOOD;
+ }
+ }
+
+ *buffer_id_return = -1;
+ *buffer_addr_return = NULL;
+ if (bytes_read == 0)
+ return SANE_STATUS_EOF;
+ else
+ return SANE_STATUS_IO_ERROR;
+}
+
+/** Pass a filled shared memory buffer to the reader process.
+ *
+ * @param shm_channel Shared memory channel object.
+ * @param buffer_id Buffer identifier from shm_channel_writer_put_buffer().
+ * @param buffer_bytes Number of data bytes in the buffer.
+ *
+ * @return
+ * - SANE_STATUS_GOOD - the buffer was successfully queued.
+ * - SANE_STATUS_IO_ERROR - the reader process has closed its half of the
+ * channel, or another I/O error occured.
+ */
+SANE_Status
+shm_channel_writer_put_buffer (Shm_Channel * shm_channel,
+ SANE_Int buffer_id, SANE_Int buffer_bytes)
+{
+ SANE_Byte buf_index;
+ int bytes_written;
+
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_writer_put_buffer");
+
+ if (buffer_id < 0 || buffer_id >= shm_channel->buf_count)
+ {
+ DBG (3, "shm_channel_writer_put_buffer: BUG: buffer_id=%d\n",
+ buffer_id);
+ return SANE_STATUS_INVAL;
+ }
+
+ shm_channel->buffer_bytes[buffer_id] = buffer_bytes;
+
+ buf_index = (SANE_Byte) buffer_id;
+ do
+ bytes_written = write (shm_channel->writer_put_pipe[1], &buf_index, 1);
+ while ((bytes_written == 0) || (bytes_written == -1 && errno == EINTR));
+
+ if (bytes_written == 1)
+ return SANE_STATUS_GOOD;
+ else
+ return SANE_STATUS_IO_ERROR;
+}
+
+/** Close the writing half of the shared memory channel.
+ *
+ * @param shm_channel Shared memory channel object.
+ */
+SANE_Status
+shm_channel_writer_close (Shm_Channel * shm_channel)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_writer_close");
+
+ shm_channel_fd_safe_close (&shm_channel->writer_put_pipe[1]);
+
+ return SANE_STATUS_GOOD;
+}
+
+
+/** Initialize the shared memory channel in the reader process.
+ *
+ * This function should be called after the fork in the process which will
+ * read data from the channel.
+ *
+ * @param shm_channel Shared memory channel object.
+ */
+SANE_Status
+shm_channel_reader_init (Shm_Channel * shm_channel)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_init");
+
+ shm_channel_fd_safe_close (&shm_channel->writer_put_pipe[1]);
+
+ /* Don't close reader_put_pipe[0] here. Otherwise, if the channel writer
+ * process dies early, this process might get SIGPIPE - and I don't want to
+ * mess with signals in the main process. */
+ /* shm_channel_fd_safe_close (&shm_channel->reader_put_pipe[0]); */
+
+ return SANE_STATUS_GOOD;
+}
+
+#if 0
+/** Set non-blocking or blocking mode for the reading half of the shared memory
+ * channel.
+ *
+ * @param shm_channel Shared memory channel object.
+ * @param non_blocking SANE_TRUE to make the channel non-blocking, SANE_FALSE
+ * to set blocking mode.
+ *
+ * @return
+ * - SANE_STATUS_GOOD - the requested mode was set successfully.
+ * - SANE_STATUS_IO_ERROR - error setting the requested mode.
+ */
+SANE_Status
+shm_channel_reader_set_io_mode (Shm_Channel * shm_channel,
+ SANE_Bool non_blocking)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_set_io_mode");
+
+ return shm_channel_fd_set_non_blocking (shm_channel->writer_put_pipe[0],
+ non_blocking);
+}
+
+/** Get the file descriptor which will signal when some data is available in
+ * the shared memory channel.
+ *
+ * The returned file descriptor can be used in select() or poll(). When one of
+ * these functions signals that the file descriptor is ready for reading,
+ * shm_channel_reader_get_buffer() should return some data without blocking.
+ *
+ * @param shm_channel Shared memory channel object.
+ * @param fd_return The returned file descriptor.
+ *
+ * @return
+ * - SANE_STATUS_GOOD - the file descriptor was returned.
+ */
+SANE_Status
+shm_channel_reader_get_select_fd (Shm_Channel * shm_channel,
+ SANE_Int * fd_return)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_get_select_fd");
+
+ *fd_return = shm_channel->writer_put_pipe[0];
+
+ return SANE_STATUS_GOOD;
+}
+#endif
+
+/** Start reading from the shared memory channel.
+ *
+ * A newly initialized shared memory channel is stopped - the writer process
+ * will block on shm_channel_writer_get_buffer(). This function will pass all
+ * available buffers to the writer process, starting the transfer through the
+ * channel.
+ *
+ * @param shm_channel Shared memory channel object.
+ */
+SANE_Status
+shm_channel_reader_start (Shm_Channel * shm_channel)
+{
+ int i, bytes_written;
+ SANE_Byte buffer_id;
+
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_start");
+
+ for (i = 0; i < shm_channel->buf_count; ++i)
+ {
+ buffer_id = i;
+ do
+ bytes_written =
+ write (shm_channel->reader_put_pipe[1], &buffer_id, 1);
+ while ((bytes_written == 0) || (bytes_written == -1 && errno == EINTR));
+
+ if (bytes_written == -1)
+ {
+ DBG (3, "shm_channel_reader_start: write error at buffer %d: %s\n",
+ i, strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+/** Get the next shared memory buffer passed from the writer process.
+ *
+ * If the channel was not set to non-blocking mode, this function will block
+ * until the data buffer arrives from the writer process. In non-blocking mode
+ * this function will place NULL in @a *buffer_addr_return and return
+ * SANE_STATUS_GOOD if a buffer is not available immediately.
+ *
+ * After successful completion of this function (return value is
+ * SANE_STATUS_GOOD and @a *buffer_addr_return is not NULL) the reader process
+ * should process the data in the buffer and then call
+ * shm_channel_reader_put_buffer() to release the buffer.
+ *
+ * @param shm_channel Shared memory channel object.
+ * @param buffer_id_return Returned buffer identifier.
+ * @param buffer_addr_return Returned buffer address.
+ * @param buffer_bytes_return Returned number of data bytes in the buffer.
+ *
+ * @return
+ * - SANE_STATUS_GOOD - no error. If the channel was in non-blocking mode, @a
+ * *buffer_id_return may be NULL, indicating that no data was available.
+ * Otherwise, @a *buffer_id_return, @a *buffer_addr_return and @a
+ * *buffer_bytes return are filled with valid values.
+ * - SANE_STATUS_EOF - the writer process has closed its half of the channel.
+ * - SANE_STATUS_IO_ERROR - an I/O error occured.
+ */
+SANE_Status
+shm_channel_reader_get_buffer (Shm_Channel * shm_channel,
+ SANE_Int * buffer_id_return,
+ SANE_Byte ** buffer_addr_return,
+ SANE_Int * buffer_bytes_return)
+{
+ SANE_Byte buf_index;
+ int bytes_read;
+
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_get_buffer");
+
+ do
+ bytes_read = read (shm_channel->writer_put_pipe[0], &buf_index, 1);
+ while (bytes_read == -1 && errno == EINTR);
+
+ if (bytes_read == 1)
+ {
+ SANE_Int index = buf_index;
+ if (index < shm_channel->buf_count)
+ {
+ *buffer_id_return = index;
+ *buffer_addr_return = shm_channel->buffers[index];
+ *buffer_bytes_return = shm_channel->buffer_bytes[index];
+ return SANE_STATUS_GOOD;
+ }
+ }
+
+ *buffer_id_return = -1;
+ *buffer_addr_return = NULL;
+ *buffer_bytes_return = 0;
+ if (bytes_read == 0)
+ return SANE_STATUS_EOF;
+ else
+ return SANE_STATUS_IO_ERROR;
+}
+
+/** Release a shared memory buffer received by the reader process.
+ *
+ * This function must be called after shm_channel_reader_get_buffer() to
+ * release the buffer and make it available for transferring the next portion
+ * of data.
+ *
+ * After calling this function the reader process must not access the buffer
+ * contents; any data which may be needed later should be copied into some
+ * other place beforehand.
+ *
+ * @param shm_channel Shared memory channel object.
+ * @param buffer_id Buffer identifier from shm_channel_reader_get_buffer().
+ *
+ * @return
+ * - SANE_STATUS_GOOD - the buffer was successfully released.
+ * - SANE_STATUS_IO_ERROR - the writer process has closed its half of the
+ * channel, or an unexpected I/O error occured.
+ */
+SANE_Status
+shm_channel_reader_put_buffer (Shm_Channel * shm_channel, SANE_Int buffer_id)
+{
+ SANE_Byte buf_index;
+ int bytes_written;
+
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_put_buffer");
+
+ if (buffer_id < 0 || buffer_id >= shm_channel->buf_count)
+ {
+ DBG (3, "shm_channel_reader_put_buffer: BUG: buffer_id=%d\n",
+ buffer_id);
+ return SANE_STATUS_INVAL;
+ }
+
+ buf_index = (SANE_Byte) buffer_id;
+ do
+ bytes_written = write (shm_channel->reader_put_pipe[1], &buf_index, 1);
+ while ((bytes_written == 0) || (bytes_written == -1 && errno == EINTR));
+
+ if (bytes_written == 1)
+ return SANE_STATUS_GOOD;
+ else
+ return SANE_STATUS_IO_ERROR;
+}
+
+#if 0
+/** Close the reading half of the shared memory channel.
+ *
+ * @param shm_channel Shared memory channel object.
+ */
+SANE_Status
+shm_channel_reader_close (Shm_Channel * shm_channel)
+{
+ SHM_CHANNEL_CHECK (shm_channel, "shm_channel_reader_close");
+
+ shm_channel_fd_safe_close (&shm_channel->reader_put_pipe[1]);
+
+ return SANE_STATUS_GOOD;
+}
+#endif
+/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */