Imported Upstream version 3.2.0
[debian/amanda] / device-src / device.h
index 262be39e4f99794163068844c0e2b0ed8b37c980..d7104ff6b14f1406e9d64526fcd921bad73b6ba8 100644 (file)
@@ -1,20 +1,20 @@
 /*
- * Copyright (c) 2005 Zmanda, Inc.  All Rights Reserved.
- * 
- * This library is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License version 2.1 as 
- * published by the Free Software Foundation.
- * 
- * This library is distributed in the hope that it will be useful, but
+ * Copyright (c) 2007, 2008, 2009, 2010 Zmanda, Inc.  All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * 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 Lesser General Public
- * License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
- * 
- * Contact information: Zmanda Inc., 505 N Mathlida Ave, Suite 120
+ * 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
+ *
+ * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
  */
 
@@ -30,6 +30,8 @@
 
 #include "property.h"
 #include "fileheader.h"
+#include "directtcp.h"
+#include "directtcp-connection.h"
 
 /* Device API version. */
 #define DEVICE_API_VERSION 0
@@ -52,316 +54,306 @@ typedef enum {
 /*
  * Type checking and casting macros
  */
+GType  device_get_type (void);
 #define TYPE_DEVICE    (device_get_type())
 #define DEVICE(obj)    G_TYPE_CHECK_INSTANCE_CAST((obj), device_get_type(), Device)
 #define DEVICE_CONST(obj)      G_TYPE_CHECK_INSTANCE_CAST((obj), device_get_type(), Device const)
 #define DEVICE_CLASS(klass)    G_TYPE_CHECK_CLASS_CAST((klass), device_get_type(), DeviceClass)
 #define IS_DEVICE(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), device_get_type ())
-
 #define DEVICE_GET_CLASS(obj)  G_TYPE_INSTANCE_GET_CLASS((obj), device_get_type(), DeviceClass)
 
 typedef struct DevicePrivate_s DevicePrivate;
 
+/* See Amanda::Device POD for a description of these constants */
+typedef enum {
+    DEVICE_STATUS_SUCCESS          = 0,
+    DEVICE_STATUS_DEVICE_ERROR     = (1 << 0),
+    DEVICE_STATUS_DEVICE_BUSY      = (1 << 1),
+    DEVICE_STATUS_VOLUME_MISSING   = (1 << 2),
+    DEVICE_STATUS_VOLUME_UNLABELED = (1 << 3),
+    DEVICE_STATUS_VOLUME_ERROR     = (1 << 4),
+    DEVICE_STATUS_FLAGS_MAX        = (1 << 5)
+} DeviceStatusFlags;
+
+#define DEVICE_STATUS_FLAGS_MASK (DEVICE_STATUS_MAX-1)
+#define DEVICE_STATUS_FLAGS_TYPE (device_status_flags_get_type())
+GType device_status_flags_get_type(void);
+
+/* a callback to prolong an operation */
+typedef gboolean (* ProlongProc)(gpointer data);
+
 /*
  * Main object structure
  */
-typedef struct {
+typedef struct Device {
     GObject __parent__;
 
     /* You can peek at the stuff below, but only subclasses should
        change these values.*/
 
-    /* What file, block are we at? (and are we in the middle of a
-     * file?) This is automatically updated by
-     * the default implementations of start_file, finish_file,
-     * write_block, read_block, seek_file, and seek_block. */
+    /* What file, block are we at? (and are we in the middle of a file?) */
     int file;
     guint64 block;
     gboolean in_file;
-    /* Holds the user-specified device name. */
+
+    /* Holds the user-specified device name, which may be an alias */
     char * device_name;
-    /* Holds the user-specified access-mode. */
+
+    /* Holds the user-specified access-mode, or ACCESS_NULL if the device
+     * has not yet been started*/
     DeviceAccessMode access_mode;
+
     /* In reading mode, FALSE unless all the data from the current file
-     * was successfully read. */
+     * was successfully read.  In writing mode, TRUE if the last byte
+     * of a file has been written by write_from_connection. */
     gboolean is_eof;
+
+    /* In writing mode, indicates that the volume is at (or near, if possible)
+     * EOM. */
+    gboolean is_eom;
+
     /* Holds the label and time of the currently-inserted volume,
      * or NULL if it has not been read/written yet. */
     char * volume_label;
     char * volume_time;
 
+    /* The most recently read volume header, or NULL if no header was
+     * read from this device.  Callers can use this to glean information
+     * about the volume beyond volume_label and volume_time.  */
+    dumpfile_t *volume_header;
+
+    /* The latest status for the device */
+    DeviceStatusFlags status;
+
+    /* device block-size ranges.  These are also available as properties,
+     * and by default users can set block_size via property BLOCK_SIZE.
+     * Writers should use block_size, and readers should initially use
+     * block_size, and expand buffers as directed by read_block. */
+    gsize min_block_size;
+    gsize max_block_size;
+    gsize block_size;
+
+    /* surety and source for the block size; if you set block_size directly,
+     * set these, too! */
+    PropertySurety block_size_surety;
+    PropertySource block_size_source;
+
     DevicePrivate * private;
 } Device;
 
-/* Pointer to factory function for device types. The factory functions
-   take control of their arguments, which should be dynamically
-   allocated. The factory should call open_device() with this
-   device_name. */
-typedef Device* (*DeviceFactory)(char * device_type,
-                                 char * device_name);
+/* Pointer to factory function for device types.
+ *
+ * device_name is the full name ("tape:/dev/nst0")
+ * device_prefix is the prefix ("tape")
+ * device_node is what follows the prefix ("/dev/nst0")
+ *
+ * The caller retains responsibility to free or otherwise handle
+ * the passed strings.
+ */
+typedef Device* (*DeviceFactory)(char *device_name,
+                                char * device_prefix,
+                                char * device_node);
 
 /* This function registers a new device with the allocation system.
  * Call it after you register your type with the GLib type system.
- * This function takes ownership of the strings inside device_prefix_list,
- * but not the device_prefix_list itself. */
+ * This function assumes that the strings in device_prefix_list are
+ * statically allocated. */
 extern void register_device(DeviceFactory factory,
                             const char ** device_prefix_list);
 
-/* This structure is a Flags (bitwise OR of values). Zero indicates success;
- * any other value indicates some kind of problem reading the label. If
- * multiple bits are set, it does not necessarily indicate that /all/ of
- * the specified issues occured, but rather that /at least one/ did. */
-typedef enum {
-    /* When changing, Also update read_label_status_flags_values in device.c */
-    READ_LABEL_STATUS_SUCCESS          = 0,
-    READ_LABEL_STATUS_DEVICE_MISSING   = (1 << 0),
-    READ_LABEL_STATUS_DEVICE_ERROR     = (1 << 1),
-    READ_LABEL_STATUS_VOLUME_MISSING   = (1 << 2),
-    READ_LABEL_STATUS_VOLUME_UNLABELED = (1 << 3),
-    READ_LABEL_STATUS_VOLUME_ERROR     = (1 << 4),
-    READ_LABEL_STATUS_FLAGS_MAX              = (1 << 5)
-} ReadLabelStatusFlags;
-
-#define READ_LABEL_STATUS_FLAGS_MASK (READ_LABEL_STATUS_MAX-1)
-#define READ_LABEL_STATUS_FLAGS_TYPE (read_label_status_flags_get_type())
-GType read_label_status_flags_get_type(void);
-
 /*
  * Class definition
  */
 typedef struct _DeviceClass DeviceClass;
 struct _DeviceClass {
     GObjectClass __parent__;
-    gboolean (* open_device) (Device * self,
-                              char * device_name); /* protected */
-    ReadLabelStatusFlags (* read_label)(Device * self);
+    void (* open_device) (Device * self, char * device_name,
+                   char * device_prefix, char * device_node);
+    gboolean (* configure) (Device * self, gboolean use_global_config);
+    DeviceStatusFlags (* read_label)(Device * self);
     gboolean (* start) (Device * self, DeviceAccessMode mode,
                         char * label, char * timestamp);
-    gboolean (* start_file) (Device * self, const dumpfile_t * info);
-    gboolean (* write_block) (Device * self, guint size, gpointer data,
-                              gboolean last_block);
-    gboolean (* write_from_fd) (Device * self, int fd);
+    gboolean (* start_file) (Device * self, dumpfile_t * info);
+    gboolean (* write_block) (Device * self, guint size, gpointer data);
     gboolean (* finish_file) (Device * self);
     dumpfile_t* (* seek_file) (Device * self, guint file);
     gboolean (* seek_block) (Device * self, guint64 block);
     int (* read_block) (Device * self, gpointer buf, int * size);
-    gboolean (* read_to_fd) (Device * self, int fd);
-    gboolean (* property_get) (Device * self, DevicePropertyId id,
-                               GValue * val);
-    gboolean (* property_set) (Device * self, DevicePropertyId id,
-                               GValue * val);
+    gboolean (* property_get_ex) (Device * self, DevicePropertyId id,
+                                 GValue * val,
+                                 PropertySurety *surety,
+                                 PropertySource *source);
+    gboolean (* property_set_ex) (Device * self,
+                                 DevicePropertyId id,
+                                 GValue * val,
+                                 PropertySurety surety,
+                                 PropertySource source);
     gboolean (* recycle_file) (Device * self, guint filenum);
+    gboolean (* erase) (Device * self);
+    gboolean (* eject) (Device * self);
     gboolean (* finish) (Device * self);
+
+    gboolean (* listen)(Device *self, gboolean for_writing, DirectTCPAddr **addrs);
+    gboolean (* accept)(Device *self, DirectTCPConnection **conn,
+                       ProlongProc prolong, gpointer prolong_data);
+    gboolean (* connect)(Device *self, gboolean for_writing, DirectTCPAddr *addrs,
+                       DirectTCPConnection **conn, ProlongProc prolong,
+                       gpointer prolong_data);
+    gboolean (* write_from_connection)(Device *self, guint64 size, guint64 *actual_size);
+    gboolean (* read_to_connection)(Device *self, guint64 size, guint64 *actual_size);
+    gboolean (* use_connection)(Device *self, DirectTCPConnection *conn);
+
+    /* array of DeviceProperty objects for this class, keyed by ID */
+    GArray *class_properties;
+
+    /* The return value of device_property_get_list */
+    GSList * class_properties_list;
+
+    /* TRUE if the directtcp methods are implemented by this device class */
+    gboolean directtcp_supported;
 };
 
+/*
+ * Device Instantiation
+ */
+
+/* Return the unaliased device name of a device.
+ * The returned string must not be freed by the caller.
+ */
+char*          device_unaliased_name(char * device_name);
+
+/* This is how you get a new Device. Pass in a device name or alias.
+ *
+ * A Device is *always* returned, even for an invalid device name. You
+ * must check the resulting device->status to know if the device is valid
+ * to be used. If device->status is not DEVICE_STATUS_SUCCESS, then there
+ * was an error opening the device.
+ *
+ * Note that the Amanda configuration must be initialized, as this function
+ * looks for device definitions and other configuration information.
+ */
+Device*                device_open     (char * device_name);
+
+/* As a special case, a RAIT device can be created from a collection of child
+ * devices.  This is used by the RAIT changer, for example.  This function is
+ * implemented in rait-device.c.  */
+Device*                rait_device_open_from_children(GSList *child_devices);
+
+/* Once you have a new device, you should configure it.  This sets properties
+ * on the device based on the user's configuation.  If USE_GLOBAL_CONFIG is
+ * true, then any global device_property parameters are processed, along with
+ * tapetype and othe relevant parameters.
+ */
+gboolean device_configure(Device *self, gboolean use_global_config);
 
 /*
- * Public methods
+ * Error Handling
+ */
+
+/* return the error message or the string "Unknown Device error".  The
+ * string remains the responsibility of the Device, and should not
+ * be freed by the caller. */
+char *device_error(Device * self);
+
+/* return a string version of the status.  The string remains the
+ * responsibility of the Device, and should not be freed by the
+ * caller. */
+char *device_status_error(Device * self);
+
+/* Return errmsg if it is set or a string version of the status.  The
+ * string remains the responsibility of the Device, and should not
+ * be freed by the caller. */
+char *device_error_or_status(Device * self);
+
+/* Set the error message for this device; for use internally to the
+ * API.  The string becomes the responsibility of the Device.  If
+ * ERRMSG is NULL, the message is cleared.  Note that the given flags
+ * are OR'd with any existing status flags. */
+void device_set_error(Device * self, char *errmsg, DeviceStatusFlags new_flags);
+
+/* Mostly for internal use, this is a boolean check to see whether a given
+ * device is in an error state.  If this is TRUE, most operations on the
+ * device will fail.
  *
- * Note to implementors: The default implementation of many of these
- * methods does not follow the documentation. For example, the default
- * implementation of device_read_block will always return -1, but
- * nonetheless update the block index in the Device structure. In
- * general, it is OK to chain up to the default implmentation after
- * successfully implementing whatever appears below. The particulars
- * of what the default implementations do is documented in device.c.
+ * The check is for DEVICE_STATUS_DEVICE_ERROR *alone*; if any other bits
+ * (e.g., VOLUME_UNLABELED) are set, then the device may not actually be in
+ * an error state.
  */
-GType  device_get_type (void);
+#define device_in_error(dev) \
+    ((DEVICE(dev))->status == DEVICE_STATUS_DEVICE_ERROR)
 
-/* This is how you get a new Device. Pass in a device name like
- * file:/path/to/storage, and (assuming everything goes OK) you will get
- * back a nice happy Device* that you can do operations on. Note that you
- * must device_start() it before you can do anything besides talk about
- * properties or read the label. device_name remains the responsibility
- * of the caller. */
-Device*        device_open     (char * device_name);
-
-/* This instructs the device to read the label on the current
- * volume. device->volume_label will not be initalized until after this
- * is called. You are encouraged to read the label only after setting any
- * properties that may affect the label-reading process. */
-ReadLabelStatusFlags        device_read_label (Device * self);
-
-/* This tells the Device that it's OK to start reading and writing
- * data. Before you call this, you can only call
- * device_property_{get, set} and device_read_label. You can only call
- * this a second time if you call device_finish() first.
+/*
+ * Public methods
  *
- * You should pass a label and timestamp if and only if you are
- * opening in WRITE mode (not READ or APPEND). The label and timestamp
- * remain the caller's responsibility in terms of memory management. The
- * passed timestamp may be NULL, in which case it will be filled in with 
- * the current time. */
+ * See the Amanda::Device POD for more information here
+ */
+
+DeviceStatusFlags        device_read_label (Device * self);
 gboolean       device_start    (Device * self,
                                  DeviceAccessMode mode, char * label,
                                  char * timestamp);
-
-/* This undoes device_start, returning you to the NULL state. Do this
- * if you want to (for example) change access mode.
- * 
- * Note to subclass implementors: Call this function first from your
- * finalization function. */
 gboolean       device_finish   (Device * self);
-
-/* But you can't write any data until you call this function, too.
- * This function does not take ownership of the passed dumpfile_t; you must
- * free it yourself. */
 gboolean        device_start_file       (Device * self,
-                                         const dumpfile_t * jobInfo);
-
-guint           device_write_min_size   (Device * self);
-guint           device_write_max_size   (Device * self);
-guint           device_read_max_size   (Device * self);
-
-/* Does what you expect. size had better be inside the block size
- * range, or this function will write nothing.
- *
- * The short_block parameter needs some additional explanation: If
- * short_block is set to TRUE, then this function will accept a write
- * smaller than the minimum block size, subject to the following
- * caveats:
- * % The block may be padded with NULL bytes, which will be present on
- *   restore.
- * % device_write_block will automatically call device_finish_file()
- *   after writing this short block.
- * It is permitted to use short_block with a block that is not short;
- * in this case, it is equivalent to calling device_write() and then
- * calling device_finish_file(). */
+                                         dumpfile_t * jobInfo);
 gboolean       device_write_block      (Device * self,
                                          guint size,
-                                         gpointer data,
-                                         gboolean short_block);
-
-/* This will drain the given fd (reading until EOF), and write the
- * resulting data out to the device using maximally-sized blocks. */
-gboolean       device_write_from_fd    (Device * self,
-                                       int fd);
-
-/* Call this when you are finished writing a file. This function will
- * write a filemark or the local equivalent, flush the buffers, and do
- * whatever dirty work needs to be done at such a point in time. */
+                                         gpointer data);
 gboolean       device_finish_file      (Device * self);
-
-/* For reading only: Seeks to the beginning of a particular
- * filemark. Only do this when reading; opening in
- * ACCESS_WRITE will start you out at the first file, and opening in
- * ACCESS_APPEND will automatically seek to the end of the medium.
- * 
- * If the requested file doesn't exist, this function will seek to the
- * next-numbered valid file. You can check where this function seeked to
- * by examining the file field of the Device structure. If the requested
- * file number is exactly one more than the last valid file, this
- * function returns a TAPEEND header.
- *
- * If an error occurs or if the requested file is two or more beyond the
- * last valid file, this function returns NULL.
- *
- * Example results for a volume that has only files 1 and 3:
- * 1 -> Seeks to file 1
- * 2 -> Seeks to file 3
- * 3 -> Seeks to file 3
- * 4 -> Returns TAPEEND
- * 5 -> Returns NULL
- *
- * The returned dumpfile_t is yours to keep, at no extra charge. */
 dumpfile_t*    device_seek_file        (Device * self,
                                        guint file);
-
-/* After you have called device_seek_file (and /only/ after having
- * called device_seek_file), you can call this to seek to a particular
- * block inside the file. It works like SEEK_SET, only in blocks. */
 gboolean       device_seek_block       (Device * self,
                                        guint64 block);
-
-/* After you have called device_seek_file and/or device_seek_block,
- * you can start calling this function. It always reads exactly one whole
- * block at a time, however big that might be. You must pass in a buffer and
- * specify its size. If the buffer is big enough, the read is
- * performed, and both *size and the return value are equal to the
- * number of bytes actually read. If the buffer is not big enough, then
- * no read is performed, the function returns 0, and *size is set
- * to the minimum buffer size required to read the next block. If an
- * error occurs, the function returns -1  and *size is left unchanged.
- * 
- * It is not an error if buffer == NULL and *size == 0. This should be
- * treated as a query as to the possible size of the next block. */
-int    device_read_block       (Device * self,
-                                 gpointer buffer,
-                                 int * size);
-
-/* This is the reading equivalent of device_write_from_fd(). It will
- * read from the device from the current location until end of file,
- * and drains the results out into the specified fd. Returns FALSE if
- * there is a problem writing to the fd. */
-gboolean       device_read_to_fd       (Device * self,
-                                       int fd);
-
-/* This function tells you what properties are supported by this
- * device, and when you are allowed to get and set them. The return
- * value is an array of DeviceProperty structs. The last struct in
- * the array is zeroed, so you know when the end is (check the
- * pointer element "base"). The return value from this function on any
- * given object (or physical device) should be invariant. */
-const DeviceProperty *         device_property_get_list        (Device * self);
-
-/* These functions get or set a particular property. The val should be
- * compatible with the DevicePropertyBase associated with the given
- * DevicePropertyId, and this function should only be called when
- * DeviceProperty.access says it is OK. Otherwise you will get an
- * error and not the tasty property action you wanted. */
-gboolean       device_property_get     (Device * self,
+int    device_read_block       (Device * self, gpointer buffer, int * size);
+const GSList * device_property_get_list        (Device * self);
+gboolean       device_property_get_ex  (Device * self,
                                          DevicePropertyId id,
-                                         GValue * val);
-gboolean       device_property_set     (Device * self,
+                                         GValue * val,
+                                        PropertySurety *surety,
+                                        PropertySource *source);
+#define                device_property_get(self, id, val) \
+    device_property_get_ex((self), (id), (val), NULL, NULL)
+gboolean       device_property_set_ex  (Device * self,
                                          DevicePropertyId id,
-                                         GValue * val);
-
-/* On devices that support it (check PROPERTY_PARTIAL_DELETION),
- * this will free only the space associated with a particular file. 
- * This way, you can apply a different retention policy to every file
- * on the volume, appending new data at the end and recycling anywhere
- * in the middle -- even simultaneously (via different Device
- * handles)! Note that you generally can't recycle a file that is presently in
- * use (being read or written).
- *
- * To use this, open the device as DEVICE_MODE_APPEND. But you don't
- * have to call device_start_file(), unless you want to write some
- * data, too. */
+                                         GValue * val,
+                                        PropertySurety surety,
+                                        PropertySource source);
+#define                device_property_set(self, id, val) \
+    device_property_set_ex((self), (id), (val), \
+           PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_USER)
 gboolean       device_recycle_file     (Device * self,
                                        guint filenum);
 
-/* Protected methods. Don't call these except in subclass implementations. */
+gboolean       device_erase    (Device * self);
+gboolean       device_eject    (Device * self);
 
-/* Registers a new device / property pair. Every superclass of Device
- * should call this in its init() function. At the moment, any
- * particular property Id can only be registered once per object.
- *
- * If you want to register a standard response to a property (e.g.,
- * whether or not the device supports compression), you can pass a
- * non-NULL response. Then the default implementation of
- * device_get_property (which you may override) will return this
- * response.
- * The contents of prop and response are copied into a private array, so the
- * calling function retains ownership of all arguments.
- */
-void            device_add_property(Device * self, DeviceProperty * prop,
-                                    GValue * response);
+#define device_directtcp_supported(self) (DEVICE_GET_CLASS((self))->directtcp_supported)
+gboolean device_listen(Device *self, gboolean for_writing, DirectTCPAddr **addrs);
+gboolean device_accept(Device *self, DirectTCPConnection **conn,
+                ProlongProc prolong, gpointer prolong_data);
+gboolean device_connect(Device *self, gboolean for_writing, DirectTCPAddr *addrs,
+                       DirectTCPConnection **conn, ProlongProc prolong,
+                       gpointer prolong_data);
+gboolean device_write_from_connection(Device *self, guint64 size, guint64 *actual_size);
+gboolean device_read_to_connection(Device *self, guint64 size, guint64 *actual_size);
+gboolean device_use_connection(Device *self, DirectTCPConnection *conn);
+
+/* Protected methods. Don't call these except in subclass implementations. */
 
 /* This method provides post-construction initalization once the
  * device name is known. It should only be used by Device
  * factories. It is provided here as a virtual method (instead of
  * a static function) because some devices may want to chain
  * initilization to their parents. */
-gboolean device_open_device (Device * self,
-                             char * device_name);
-
-/* Builds a proper header based on device block size possibilities.
- * If non-null, size is filled in with the number of bytes that should
- * be written.
- * If non-null, oneblock is filled in with TRUE if the header will fit
- * in a single Device block (FALSE otherwise). */
+void device_open_device (Device * self, char *device_name, char *device_type, char *device_node);
+
+/* Builds a proper header of between *size and self->block_size bytes.
+ * Returns NULL if the header does not fit in a single block.  The result
+ * must be free'd.  If size is NULL, the block size is used.
+ *
+ * If size is not NULL, *size is set to the actual size of the generated header.
+ */
 char * device_build_amanda_header(Device * self, const dumpfile_t * jobinfo,
-                                  int * size, gboolean * oneblock);
+                                  size_t *size);
 
 /* Does what you expect. You have to free the returned header. Ensures
    that self->volume_time matches the header written to tape. */
@@ -371,13 +363,54 @@ dumpfile_t * make_tapestart_header(Device * self, char * label,
 /* Does what you expect. Uses the current time. */
 dumpfile_t * make_tapeend_header(void);
 
-/* Set up first-run properties from loaded configuration file, including
- * DEVICE_MAX_VOLUME_USAGE property based on the tapetype. */
-void device_set_startup_properties_from_config(Device * device);
-
 /* Erase any stored volume information. Use this if something happens (e.g.,
  * a property is set) that voids previously-read volume details.
  * This function is a NOOP unless the device is in the NULL state. */
 void device_clear_volume_details(Device * device);
 
+/* Property Handling */
+
+/* Registers a property for a new device class; device drivers' GClassInitFunc
+ * should call this function for each device-specific property of the class.
+ * If either getter or setter is NULL, then the corresponding operation will
+ * return FALSE.
+ *
+ * Note that this will replace any existing registration (e.g., from a parent
+ * class).
+ */
+void device_class_register_property(DeviceClass *klass, DevicePropertyId id,
+                                   PropertyAccessFlags access,
+                                   PropertyGetFn getter,
+                                   PropertySetFn setter);
+
+/* Set a 'simple' property on the device.  This tucks the value away in the
+ * object, to be retrieved by device_simple_property_get_fn.  This is most
+ * often used in GInstanceInit functions, but can be used at any time to set or
+ * change the value of a simple property */
+gboolean device_set_simple_property(Device *self, DevicePropertyId id,
+                               GValue *val, PropertySurety surety,
+                               PropertySource source);
+
+/* Get a simple property set with device_set_simple_property.  This is a little
+ * bit quicker than calling device_property_get_ex(), and does not affect the
+ * device's error state.  Returns FALSE if the property has not been set.
+ * Surety and source are output parameters and will be ignored if they are
+ * NULL. */
+gboolean device_get_simple_property(Device *self, DevicePropertyId id,
+                                   GValue *val, PropertySurety *surety,
+                                   PropertySource *source);
+
+/* A useful PropertySetFn.  If your subclass also needs to intercept sets, for
+ * example to flush a cache or update a member variable, then write a stub
+ * function which "calls up" to this function. */
+gboolean device_simple_property_set_fn(Device *self, DevicePropertyBase *base,
+                                      GValue *val, PropertySurety surety,
+                                      PropertySource source);
+
+/* A useful PropertyGetFn -- returns the value, source, and surety set with
+ * device_set_simple_property */
+gboolean device_simple_property_get_fn(Device *self, DevicePropertyBase *base,
+                                      GValue *val, PropertySurety *surety,
+                                      PropertySource *source);
+
 #endif /* DEVICE_H */