right that moment ("hands-off reading").
Automatically rescan when anything is mounted or unmounted.
Both are intended to improve compatibility with removable storage.
<p>This file documents recent user-visible changes to <a
href="http://www.greenend.org.uk/rjk/disorder/">DisOrder</a>.</p>
+<h2>Changes up to version 5.1</h2>
+
+<div class=section>
+
+ <h3>Removable Device Support</h3>
+
+ <div class=section>
+
+ <p>The server will now automatically initiate a rescan when a filesystem is
+ mounted or unmounted. (Use the <tt>mount_rescan</tt> option if you want to
+ suppress this behavior.)</p>
+
+ <p>The server takes care not to hold audio files open unnecessarily, so
+ that devices can be unmounted even if tracks from them are currently being
+ buffered.</p>
+
+ </div>
+
+</div>
+
<h2>Changes up to version 5.0</h2>
<div class=section>
AC_CHECK_HEADERS([db.h],[:],[
missing_headers="$missing_headers $ac_header"
])
- AC_CHECK_HEADERS([FLAC/file_decoder.h])
fi
AC_CHECK_HEADERS([dlfcn.h gcrypt.h \
getopt.h iconv.h langinfo.h \
AC_CHECK_TYPES([struct sockaddr_in6],,,[AC_INCLUDES_DEFAULT
#include <netinet/in.h>])
+# Figure out how we'll check for devices being mounted and unmounted
+AC_CACHE_CHECK([for list of mounted filesystems],[rjk_cv_mtab],[
+ if test -e /etc/mtab; then
+ rjk_cv_mtab=/etc/mtab
+ else
+ rjk_cv_mtab=none
+ fi
+])
+if test $rjk_cv_mtab != none; then
+ AC_DEFINE_UNQUOTED([PATH_MTAB],["$rjk_cv_mtab"],[path to file containing mount list])
+fi
+
# enable -Werror when we check for certain characteristics:
old_CFLAGS="${CFLAGS}"
fi
# Functions we can take or leave
-AC_CHECK_FUNCS([fls])
+AC_CHECK_FUNCS([fls getfsstat])
if test $want_server = yes; then
# <db.h> had better be version 3 or later
.RS
.TP 8
.B pcm
-
Output level for the audio device.
This is probably what you want and is the default.
.TP
.IP
For \fBapi coreaudio\fR, volume setting is not currently supported.
.TP
+.B mount_rescan yes\fR|\fBno
+Determines whether mounts and unmounts will cause an automatic rescan.
+The default is \fByes\fR.
+.TP
.B multicast_loop yes\fR|\fBno
Determines whether multicast packets are loop backed to the sending host.
The default is \fByes\fR.
heap.h \
hex.c hex.h \
hostname.c hostname.h \
+ hreader.c hreader.h \
ifreq.c ifreq.h \
inputline.c inputline.h \
kvp.c kvp.h \
{ C(lock), &type_boolean, validate_any },
{ C(mail_sender), &type_string, validate_any },
{ C(mixer), &type_string, validate_any },
+ { C(mount_rescan), &type_boolean, validate_any },
{ C(multicast_loop), &type_boolean, validate_any },
{ C(multicast_ttl), &type_integer, validate_non_negative },
{ C(namepart), &type_namepart, validate_any },
c->sox_generation = DEFAULT_SOX_GENERATION;
c->playlist_max = INT_MAX; /* effectively no limit */
c->playlist_lock_timeout = 10; /* 10s */
+ c->mount_rescan = 1;
/* Default stopwords */
if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
exit(1);
/** @brief Maximum bias */
long new_bias;
-
+
+ /** @brief Rescan on (un)mount */
+ int mount_rescan;
+
/* derived values: */
int nparts; /* number of distinct name parts */
char **parts; /* name part list */
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2010 Richard Kettlewell
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/hreader.c
+ * @brief Hands-off reader - read files without keeping them open
+ */
+#include <config.h>
+#include "hreader.h"
+#include "mem.h"
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+static int hreader_fill(struct hreader *h, off_t offset);
+
+int hreader_init(const char *path, struct hreader *h) {
+ struct stat sb;
+ if(stat(path, &sb) < 0)
+ return -1;
+ memset(h, 0, sizeof *h);
+ h->path = xstrdup(path);
+ h->size = sb.st_size;
+ h->bufsize = 65536;
+ h->buffer = xmalloc_noptr(h->bufsize);
+ return 0;
+}
+
+void hreader_close(struct hreader *h) {
+ xfree(h->path);
+ xfree(h->buffer);
+}
+
+int hreader_read(struct hreader *h, void *buffer, size_t n) {
+ int r = hreader_pread(h, buffer, n, h->read_offset);
+ if(r > 0)
+ h->read_offset += r;
+ return r;
+}
+
+int hreader_pread(struct hreader *h, void *buffer, size_t n, off_t offset) {
+ size_t bytes_read = 0;
+
+ while(bytes_read < n) {
+ // If the desired byte range is outside the file, fetch new contents
+ if(offset < h->buf_offset || offset >= h->buf_offset + (off_t)h->bytes) {
+ int r = hreader_fill(h, offset);
+ if(r < 0)
+ return -1; /* disaster! */
+ else if(r == 0)
+ break; /* end of file */
+ }
+ // Figure out how much we can read this time round
+ size_t left = h->bytes - (offset - h->buf_offset);
+ // Truncate the read if we don't want that much
+ if(left > (n - bytes_read))
+ left = n - bytes_read;
+ memcpy((char *)buffer + bytes_read,
+ h->buffer + (offset - h->buf_offset),
+ left);
+ offset += left;
+ bytes_read += left;
+ }
+ return bytes_read;
+}
+
+static int hreader_fill(struct hreader *h, off_t offset) {
+ int fd = open(h->path, O_RDONLY);
+ if(fd < 0)
+ return -1;
+ int n = pread(fd, h->buffer, h->bufsize, offset);
+ close(fd);
+ if(n < 0)
+ return -1;
+ h->buf_offset = offset;
+ h->bytes = n;
+ return n;
+}
+
+off_t hreader_seek(struct hreader *h, off_t offset, int whence) {
+ switch(whence) {
+ case SEEK_SET: break;
+ case SEEK_CUR: offset += h->read_offset; break;
+ case SEEK_END: offset += h->size; break;
+ default: einval: errno = EINVAL; return -1;
+ }
+ if(offset < 0) goto einval;
+ h->read_offset = offset;
+ return offset;
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2010 Richard Kettlewell
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/hreader.h
+ * @brief Hands-off reader - read files without keeping them open
+ */
+#ifndef HREADER_H
+#define HREADER_H
+
+#include <unistd.h>
+
+/** @brief A hands-off reader
+ *
+ * Allows files to be read without holding them open.
+ */
+struct hreader {
+ char *path; /* file to read */
+ off_t size; /* file size */
+ off_t read_offset; /* for next hreader_read() */
+ off_t buf_offset; /* offset of start of buffer */
+ char *buffer; /* input buffer */
+ size_t bufsize; /* buffer size */
+ size_t bytes; /* size of last read */
+};
+
+/** @brief Initialize a hands-off reader
+ * @param path File to read
+ * @param h Reader to initialize
+ * @return 0 on success, -1 on error
+ */
+int hreader_init(const char *path, struct hreader *h);
+
+/** @brief Close a hands-off reader
+ * @param h Reader to close
+ */
+void hreader_close(struct hreader *h);
+
+/** @brief Read some bytes
+ * @param h Reader to read from
+ * @param buffer Where to store bytes
+ * @param n Maximum bytes to read
+ * @return Bytes read, or 0 at EOF, or -1 on error
+ */
+int hreader_read(struct hreader *h, void *buffer, size_t n);
+
+/** @brief Read some bytes at a given offset
+ * @param h Reader to read from
+ * @param offset Offset to read at
+ * @param buffer Where to store bytes
+ * @param n Maximum bytes to read
+ * @return Bytes read, or 0 at EOF, or -1 on error
+ */
+int hreader_pread(struct hreader *h, void *buffer, size_t n, off_t offset);
+
+/** @brief Seek within a file
+ * @param h Reader to seek
+ * @param offset Offset
+ * @param whence SEEK_*
+ * @return Result offset
+ */
+off_t hreader_seek(struct hreader *h, off_t offset, int whence);
+
+/** @brief Return file size
+ * @param h Reader to find size of
+ * @return Size in bytes
+ */
+static inline off_t hreader_size(const struct hreader *h) {
+ return h->size;
+}
+
+/** @brief Test for end of file
+ * @param h Reader to test
+ * @return 1 at eof, 0 otherwise
+ *
+ * This tells you whether the next read will return 0 bytes, rather than
+ * whether the last read reached end of file. So it is slightly different to
+ * feof().
+ */
+static inline int hreader_eof(const struct hreader *h) {
+ return h->read_offset == h->size;
+}
+
+#endif /* HREADER_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
off_t where;
memset(f, 0, sizeof *f);
- f->fd = -1;
f->data = -1;
- if((f->fd = open(path, O_RDONLY)) < 0) goto error_errno;
+ if(hreader_init(path, f->input)) goto error_errno;
/* Read the file header
*
* offset size meaning
* 04 4 length of rest of file
* 08 4 'WAVE'
* */
- if((n = pread(f->fd, header, 12, 0)) < 0) goto error_errno;
+ if((n = hreader_pread(f->input, header, 12, 0)) < 0) goto error_errno;
else if(n < 12) goto einval;
if(strncmp(header, "RIFF", 4) || strncmp(header + 8, "WAVE", 4))
goto einval;
* 00 4 chunk ID
* 04 4 length of rest of chunk
*/
- if((n = pread(f->fd, header, 8, where)) < 0) goto error_errno;
+ if((n = hreader_pread(f->input, header, 8, where)) < 0) goto error_errno;
else if(n < 8) goto einval;
if(!strncmp(header,"fmt ", 4)) {
/* This is the format chunk
* 18 ? extra undocumented rubbish
*/
if(get32(header + 4) < 16) goto einval;
- if((n = pread(f->fd, header + 8, 16, where + 8)) < 0) goto error_errno;
+ if((n = hreader_pread(f->input, header + 8, 16, where + 8)) < 0)
+ goto error_errno;
else if(n < 16) goto einval;
f->channels = get16(header + 0x0A);
f->rate = get32(header + 0x0C);
/** @brief Close a WAV file */
void wav_destroy(struct wavfile *f) {
- if(f) {
- const int save_errno = errno;
-
- if(f->fd >= 0)
- close(f->fd);
- errno = save_errno;
- }
+ hreader_close(f->input);
}
/** @brief Visit all the data in a WAV file
size_t want = (off_t)sizeof buffer > left ? (size_t)left : sizeof buffer;
want -= want % bytes_per_frame;
- if((n = pread(f->fd, buffer, want, where)) < 0) return errno;
+ if((n = hreader_pread(f->input, buffer, want, where)) < 0) return errno;
if((size_t)n < want) return EINVAL;
if((err = callback(f, buffer, n, u))) return err;
where += n;
#ifndef WAV_H
#define WAV_H
+#include "hreader.h"
+
/** @brief WAV file access structure */
struct wavfile {
- /** @brief File descriptor onto file */
- int fd;
+ /** @brief File read handle */
+ struct hreader input[1];
/** @brief File length */
off_t length;
disorder_tracklength_la_SOURCES=tracklength.c tracklength.h \
tracklength-mp3.c tracklength-ogg.c tracklength-wav.c \
-tracklength-flac.c mad.c madshim.h ../lib/wav.h ../lib/wav.c
+tracklength-flac.c mad.c madshim.h ../lib/wav.h ../lib/wav.c \
+../lib/hreader.h ../lib/hreader.c
disorder_tracklength_la_LDFLAGS=-module
disorder_tracklength_la_LIBADD=$(LIBVORBISFILE) $(LIBMAD) $(LIBFLAC) -lm
disorderd_SOURCES=disorderd.c api.c api-server.c daemonize.c play.c \
server.c server-queue.c queue-ops.c state.c plugin.c \
- schedule.c dbparams.c background.c \
+ schedule.c dbparams.c background.c mount.c \
exports.c ../lib/memgc.c disorder-server.h
disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
*/
#include "disorder-server.h"
+#include "hreader.h"
#include <mad.h>
#include <vorbis/vorbisfile.h>
-/* libFLAC has had an API change and stupidly taken away the old API */
-#if HAVE_FLAC_FILE_DECODER_H
-# include <FLAC/file_decoder.h>
-#else
-# include <FLAC/stream_decoder.h>
-#define FLAC__FileDecoder FLAC__StreamDecoder
-#define FLAC__FileDecoderState FLAC__StreamDecoderState
-#endif
+#include <FLAC/stream_decoder.h>
#include "wav.h"
#include "speaker-protocol.h"
void (*decode)(void);
};
-/** @brief Input file */
-static int inputfd;
+static struct hreader input[1];
/** @brief Output file */
static FILE *outputfp;
remain = 0;
}
/* Read new data */
- n = read(inputfd, input_buffer + remain, (sizeof input_buffer) - remain);
+ n = hreader_read(input,
+ input_buffer + remain,
+ (sizeof input_buffer) - remain);
if(n < 0)
disorder_fatal(errno, "reading from %s", path);
/* Compute total number of bytes available */
static void decode_mp3(void) {
struct mad_decoder mad[1];
- if((inputfd = open(path, O_RDONLY)) < 0)
+ if(hreader_init(path, input))
disorder_fatal(errno, "opening %s", path);
mad_decoder_init(mad, 0/*data*/, mp3_input, 0/*header*/, 0/*filter*/,
mp3_output, mp3_error, 0/*message*/);
mad_decoder_finish(mad);
}
+static size_t ogg_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) {
+ struct hreader *h = datasource;
+
+ int n = hreader_read(h, ptr, size * nmemb);
+ if(n < 0) n = 0;
+ return n / size;
+}
+
+static int ogg_seek_func(void *datasource, ogg_int64_t offset, int whence) {
+ struct hreader *h = datasource;
+
+ return hreader_seek(h, offset, whence) < 0 ? -1 : 0;
+}
+
+static int ogg_close_func(void attribute((unused)) *datasource) {
+ return 0;
+}
+
+static long ogg_tell_func(void *datasource) {
+ struct hreader *h = datasource;
+
+ return hreader_seek(h, 0, SEEK_CUR);
+}
+
+static const ov_callbacks ogg_callbacks = {
+ ogg_read_func,
+ ogg_seek_func,
+ ogg_close_func,
+ ogg_tell_func,
+};
+
/** @brief OGG decoder */
static void decode_ogg(void) {
- FILE *fp;
+ struct hreader ogginput[1];
OggVorbis_File vf[1];
int err;
long n;
int bitstream;
vorbis_info *vi;
- if(!(fp = fopen(path, "rb")))
- disorder_fatal(errno, "cannot open %s", path);
+ hreader_init(path, ogginput);
/* There doesn't seem to be any standard function for mapping the error codes
* to strings l-( */
- if((err = ov_open(fp, vf, 0/*initial*/, 0/*ibytes*/)))
- disorder_fatal(0, "ov_fopen %s: %d", path, err);
+ if((err = ov_open_callbacks(ogginput, vf, 0/*initial*/, 0/*ibytes*/,
+ ogg_callbacks)))
+ disorder_fatal(0, "ov_open_callbacks %s: %d", path, err);
if(!(vi = ov_info(vf, 0/*link*/)))
disorder_fatal(0, "ov_info %s: failed", path);
while((n = ov_read(vf, input_buffer, sizeof input_buffer, 1/*bigendianp*/,
*
* This is a no-op here.
*/
-static void flac_metadata(const FLAC__FileDecoder attribute((unused)) *decoder,
+static void flac_metadata(const FLAC__StreamDecoder attribute((unused)) *decoder,
const FLAC__StreamMetadata attribute((unused)) *metadata,
void attribute((unused)) *client_data) {
}
/** @brief Error callback for FLAC decoder */
-static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder,
+static void flac_error(const FLAC__StreamDecoder attribute((unused)) *decoder,
FLAC__StreamDecoderErrorStatus status,
void attribute((unused)) *client_data) {
disorder_fatal(0, "error decoding %s: %s", path,
/** @brief Write callback for FLAC decoder */
static FLAC__StreamDecoderWriteStatus flac_write
- (const FLAC__FileDecoder attribute((unused)) *decoder,
+ (const FLAC__StreamDecoder attribute((unused)) *decoder,
const FLAC__Frame *frame,
const FLAC__int32 *const buffer[],
void attribute((unused)) *client_data) {
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
+static FLAC__StreamDecoderReadStatus flac_read(const FLAC__StreamDecoder attribute((unused)) *decoder,
+ FLAC__byte buffer[],
+ size_t *bytes,
+ void *client_data) {
+ struct hreader *flacinput = client_data;
+ int n = hreader_read(flacinput, buffer, *bytes);
+ if(n == 0) {
+ *bytes = 0;
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ }
+ if(n < 0) {
+ *bytes = 0;
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+ }
+ *bytes = n;
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+static FLAC__StreamDecoderSeekStatus flac_seek(const FLAC__StreamDecoder attribute((unused)) *decoder,
+ FLAC__uint64 absolute_byte_offset,
+ void *client_data) {
+ struct hreader *flacinput = client_data;
+ if(hreader_seek(flacinput, absolute_byte_offset, SEEK_SET) < 0)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+ else
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
+}
+
+static FLAC__StreamDecoderTellStatus flac_tell(const FLAC__StreamDecoder attribute((unused)) *decoder,
+ FLAC__uint64 *absolute_byte_offset,
+ void *client_data) {
+ struct hreader *flacinput = client_data;
+ off_t offset = hreader_seek(flacinput, 0, SEEK_CUR);
+ if(offset < 0)
+ return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
+ *absolute_byte_offset = offset;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+static FLAC__StreamDecoderLengthStatus flac_length(const FLAC__StreamDecoder attribute((unused)) *decoder,
+ FLAC__uint64 *stream_length,
+ void *client_data) {
+ struct hreader *flacinput = client_data;
+ *stream_length = hreader_size(flacinput);
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+}
+
+static FLAC__bool flac_eof(const FLAC__StreamDecoder attribute((unused)) *decoder,
+ void *client_data) {
+ struct hreader *flacinput = client_data;
+ return hreader_eof(flacinput);
+}
/** @brief FLAC file decoder */
static void decode_flac(void) {
-#if HAVE_FLAC_FILE_DECODER_H
- FLAC__FileDecoder *fd = 0;
- FLAC__FileDecoderState fs;
-
- if(!(fd = FLAC__file_decoder_new()))
- disorder_fatal(0, "FLAC__file_decoder_new failed");
- if(!(FLAC__file_decoder_set_filename(fd, path)))
- disorder_fatal(0, "FLAC__file_set_filename failed");
- FLAC__file_decoder_set_metadata_callback(fd, flac_metadata);
- FLAC__file_decoder_set_error_callback(fd, flac_error);
- FLAC__file_decoder_set_write_callback(fd, flac_write);
- if((fs = FLAC__file_decoder_init(fd)))
- disorder_fatal(0, "FLAC__file_decoder_init: %s", FLAC__FileDecoderStateString[fs]);
- FLAC__file_decoder_process_until_end_of_file(fd);
-#else
FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
FLAC__StreamDecoderInitStatus is;
+ struct hreader flacinput[1];
if (!sd)
disorder_fatal(0, "FLAC__stream_decoder_new failed");
-
- if((is = FLAC__stream_decoder_init_file(sd, path, flac_write, flac_metadata,
- flac_error, 0)))
- disorder_fatal(0, "FLAC__stream_decoder_init_file %s: %s",
+ if(hreader_init(path, flacinput))
+ disorder_fatal(errno, "error opening %s", path);
+
+ if((is = FLAC__stream_decoder_init_stream(sd,
+ flac_read,
+ flac_seek,
+ flac_tell,
+ flac_length,
+ flac_eof,
+ flac_write, flac_metadata,
+ flac_error,
+ flacinput)))
+ disorder_fatal(0, "FLAC__stream_decoder_init_stream %s: %s",
path, FLAC__StreamDecoderInitStatusString[is]);
FLAC__stream_decoder_process_until_end_of_stream(sd);
FLAC__stream_decoder_finish(sd);
FLAC__stream_decoder_delete(sd);
-#endif
}
/** @brief Lookup table of decoders */
/*
* This file is part of DisOrder
- * Copyright (C) 2008, 2009 Richard Kettlewell
+ * Copyright (C) 2008-2010 Richard Kettlewell
*
* 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
#define START_HARDFAIL 1 /**< @brief Track is broken. */
#define START_SOFTFAIL 2 /**< @brief Track OK, system (temporarily?) broken */
+void periodic_mount_check(ev_source *ev_);
+
+#ifndef MOUNT_CHECK_INTERVAL
+# ifdef PATH_MTAB
+// statting a file is really cheap so check once a second
+# define MOUNT_CHECK_INTERVAL 1
+# else
+// hashing getfsstat() output could be more expensive so be less aggressive
+# define MOUNT_CHECK_INTERVAL 5
+# endif
+#endif
+
#endif /* DISORDER_SERVER_H */
/*
create_periodic(ev, periodic_play_check, 1, 0);
/* Try adding a random track immediately and once every two seconds */
create_periodic(ev, periodic_add_random, 2, 1);
+ /* Issue a rescan when devices are mounted or unmouted */
+ create_periodic(ev, periodic_mount_check, MOUNT_CHECK_INTERVAL, 1);
/* enter the event loop */
n = ev_run(ev);
/* if we exit the event loop, something must have gone wrong */
--- /dev/null
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2010 Richard Kettlewell
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+/** @file server/mount.c
+ * @brief Periodically check for devices being mounted and unmounted
+ */
+#include "disorder-server.h"
+#if HAVE_GETFSSTAT
+# include <sys/param.h>
+# include <sys/ucred.h>
+# include <sys/mount.h>
+#endif
+
+#if HAVE_GETFSSTAT
+static int compare_fsstat(const void *av, const void *bv) {
+ const struct statfs *a = av, *b = bv;
+ int c;
+
+ c = memcmp(&a->f_fsid, &b->f_fsid, sizeof a->f_fsid);
+ if(c)
+ return c;
+ c = strcmp(a->f_mntonname, b->f_mntonname);
+ if(c)
+ return c;
+ return 0;
+}
+#endif
+
+void periodic_mount_check(ev_source *ev_) {
+ if(!config->mount_rescan)
+ return;
+#if HAVE_GETFSSTAT
+ /* On OS X, we keep track of the hash of the kernel's mounted
+ * filesystem list */
+ static int first = 1;
+ static unsigned char last[20];
+ unsigned char *current;
+ int nfilesystems, space;
+ struct statfs *buf;
+ gcrypt_hash_handle h;
+ gcry_error_t e;
+
+ space = getfsstat(NULL, 0, MNT_NOWAIT);
+ buf = xcalloc(space, sizeof *buf);
+ nfilesystems = getfsstat(buf, space * sizeof *buf, MNT_NOWAIT);
+ if(nfilesystems > space)
+ // The array grew between check and use! We just give up and try later.
+ return;
+ // Put into order so we get a bit of consistency
+ qsort(buf, nfilesystems, sizeof *buf, compare_fsstat);
+ if((e = gcry_md_open(&h, GCRY_MD_SHA1, 0))) {
+ disorder_error(0, "gcry_md_open: %s", gcry_strerror(e));
+ return;
+ }
+ for(int n = 0; n < nfilesystems; ++n) {
+ gcry_md_write(h, &buf[n].f_fsid, sizeof buf[n].f_fsid);
+ gcry_md_write(h, buf[n].f_mntonname, 1 + strlen(buf[n].f_mntonname));
+ }
+ current = gcry_md_read(h, GCRY_MD_SHA1);
+ if(!first && memcmp(current, last, sizeof last))
+ trackdb_rescan(ev_, 1/*check*/, 0, 0);
+ memcpy(last, current, sizeof last);
+ first = 0;
+ gcry_md_close(h);
+#elif defined PATH_MTAB
+ /* On Linux we keep track of the modification time of /etc/mtab */
+ static time_t last_mount;
+ struct stat sb;
+
+ if(stat(PATH_MTAB, &sb) >= 0) {
+ if(last_mount != 0 && last_mount != sb.st_mtime)
+ trackdb_rescan(ev_, 1/*check*/, 0, 0);
+ last_mount = sb.st_mtime;
+ }
+#endif
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/