/*
- * Copyright (c) Zmanda, Inc. All Rights Reserved.
+ * Copyright (c) 2007, 2008, 2009, 2010 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 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 library is distributed in the hope that it will be useful, but
+ * 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.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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.
+ * 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 Mathlida Ave, Suite 300
- * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
+ * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
+ * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
*/
%module "Amanda::Device"
%include "amglue/amglue.swg"
%include "exception.i"
+%import "Amanda/Header.swg";
+
+%include "Amanda/Device.pod"
%{
#include "device.h"
#include "property.h"
#include "fileheader.h"
-%}
-
-/* import dumptype_t, among others */
-%import "Amanda/Types.swg";
-
-%perlcode %{
-=head1 NAME
-
-Amanda::Device - interact with Amanda data-storage devices
-
-=head1 SYNOPSIS
-
- use Amanda::Device qw( :constants );
-
- my $dev = Amanda::Device->new($device_name);
- if ($dev->read_label() == $DEVICE_STATUS_SUCCESS) {
- print "Label on $device_name is '$dev->volume_label'\n";
- }
-
-See http://wiki.zmanda.com/index.php/Device_API for details on how Devices are used.
-
-=head1 API STATUS
-
-Stable
-
-=head1 Amanda::Device Objects
-
-See the wiki for descriptions of these functions. Note that C instance variables
-are represented by accessor functions of the same name.
-
-=over
-
-=item C<configure($use_global_config)>
-
-=item C<read_label()>
-
-=item C<start($mode, $label, $timestamp)>
-
-=item C<finish()>
-
-=item C<start_file($jobinfo)>
-
-where C<$jobinfo> is a C<dumpfile_t> (see L<Amanda::Types>)
-
-=item C<write_block($size, $data, $short_block)>
-
-Note that Perl code is not expected to handle on-device data, so there
-is currently no way to provide data to this function from Perl. This may
-change in future revisions.
-
-=item C<write_from_fd($queue_fd)>
-
-where C<$fd> is an integer file descriptor, not a filehandle
-
-=item C<finish_file()>
-
-=item C<seek_file($file)>
-
-=item C<seek_block($block)>
-
-=item C<read_block($size)>
-
-=item C<read_to_fd($queue_fd)>
-
-where C<$fd> is an integer file descriptor, not a filehandle
-
-Note that Perl code is not expected to handle on-device data, so there
-is currently no way to access the data this function returns. This may
-change in future revisions.
-
-=item C<property_list()>
-
-returns a list of properties, each represented as a hash:
-
- { 'access' => $access_flags,
- 'name' => $property_name,
- 'description' => $property_description }
-
-=item C<property_get($property_name)>
-
-returns the property, with the appropriate Perl type, or undef. In array
-context, returns the list C<($value, $surety, $source)>.
-
-=item C<property_set($property_name, $value)>
-
-=item C<property_set_ex($property_name, $value, $surety, $source)>
-
-where C<$value> is of an appropriate type for the given property, C<$surety> is
-a C<$PROPERTY_SURETY_*> constant, and C<$source> is a C<$PROPERTY_SOURCE_*>
-constant.
-
-=item C<recycle_file($filenum)>
-
-=item C<file()> (accessor function)
-
-=item C<block()> (accessor function)
-
-=item C<in_file()> (accessor function)
-
-=item C<device_name()> (accessor function)
-
-=item C<access_mode()> (accessor function)
-
-=item C<is_eof()> (accessor function)
-
-=item C<volume_label()> (accessor function)
-
-=item C<volume_time()> (accessor function)
-
-=item C<min_block_size()> (accessor function)
-
-=item C<max_block_size()> (accessor function)
-
-=item C<block_size()> (accessor function)
-
-=item C<volume_header()> (accessor function)
-
-=back
-
-=head1 CONSTANTS
-
-This module defines a large number of constants. Again, consult the
-wiki or C<device.h> for the details on their meaning. These constants
-are available from the package namespace (e.g.,
-C<Amanda::Device::ACCESS_WRITE>), or imported with the C<:constant>
-import tag.
-
-=cut
+#include "glib-util.h"
+#include "simpleprng.h"
+#include "amanda.h"
+#include "sockaddr-util.h"
%}
%init %{
/* Utility functions for typemaps, below */
+/* return a new, mortal SV corresponding to the given GValue
+ *
+ * @param value: the value to convert
+ * @returns: a new, mortal SV
+ */
static SV *
set_sv_from_gvalue(GValue *value)
{
/* complex reference types */
switch (fundamental) {
case G_TYPE_LONG:
- sv = sv_2mortal(amglue_newSVi64(g_value_get_long(value)));
- break;
+ return sv_2mortal(amglue_newSVi64(g_value_get_long(value)));
case G_TYPE_ULONG:
- sv = sv_2mortal(amglue_newSVu64(g_value_get_ulong(value)));
- break;
+ return sv_2mortal(amglue_newSVu64(g_value_get_ulong(value)));
case G_TYPE_INT64:
- sv = sv_2mortal(amglue_newSVi64(g_value_get_int64(value)));
- break;
+ return sv_2mortal(amglue_newSVi64(g_value_get_int64(value)));
case G_TYPE_UINT64:
- sv = sv_2mortal(amglue_newSVu64(g_value_get_uint64(value)));
- break;
-
- case G_TYPE_BOXED: {
- GType boxed_type = G_VALUE_TYPE(value);
- QualifiedSize qs;
- HV *hv;
-
- if (boxed_type == QUALIFIED_SIZE_TYPE) {
- qs = *(QualifiedSize*)(g_value_get_boxed(value));
-
- /* build a hash */
- hv = (HV *)sv_2mortal((SV *)newHV());
- hv_store(hv, "accuracy", 8, newSViv(qs.accuracy), 0);
- hv_store(hv, "bytes", 5, amglue_newSVi64(qs.bytes), 0);
-
- sv = newRV((SV *)hv);
- return newRV((SV *)hv);
- } else {
- warn("Unsupported boxed property type #%d", (int)boxed_type);
-
- sv = sv_newmortal();
- sv_setsv(sv, &PL_sv_undef);
- return sv;
- }
- }
+ return sv_2mortal(amglue_newSVu64(g_value_get_uint64(value)));
}
/* simple types that can be constructed with sv_set*v */
return sv;
}
+/* Given an SV and an initialized GValue, set the GValue to the value
+ * represented by the SV. The GValue's type must already be set.
+ *
+ * For basic corresponding types (string -> string, integer -> integer),
+ * the translation is straightforward. However, if the GValue is not a
+ * string, but the SV has a string value, then g_value_set_from_string will
+ * be used to parse the string.
+ *
+ * @param sv: SV to convert
+ * @param value: (input/output) destination
+ * @returns: TRUE on success
+ */
static gboolean
set_gvalue_from_sv(SV *sv, GValue *value)
{
GType fundamental = G_TYPE_FUNDAMENTAL(G_VALUE_TYPE(value));
- switch (fundamental) {
- case G_TYPE_CHAR:
- if (!SvIOK(sv)) return FALSE;
- g_value_set_char(value, SvIV(sv));
- break;
- case G_TYPE_UCHAR:
- if (!SvIOK(sv)) return FALSE;
- g_value_set_uchar(value, SvUV(sv));
- break;
+ /* if we got a string, use g_value_set_from_string to parse any funny
+ * values or suffixes */
+ if (SvPOK(sv)) {
+ if (g_value_set_from_string(value, SvPV_nolen(sv)))
+ return TRUE;
+ }
+ /* otherwise, handle numeric types with SvIV, SvNV, or the amglue_* functions */
+ switch (fundamental) {
case G_TYPE_BOOLEAN:
- if (!SvIOK(sv)) return FALSE;
g_value_set_boolean(value, SvIV(sv));
- break;
+ return TRUE;
+
+ case G_TYPE_CHAR:
+ g_value_set_char(value, amglue_SvI8(sv));
+ return TRUE;
+
+ case G_TYPE_UCHAR:
+ g_value_set_uchar(value, amglue_SvU8(sv));
+ return TRUE;
case G_TYPE_INT:
g_value_set_int(value, amglue_SvI32(sv));
- break;
+ return TRUE;
case G_TYPE_UINT:
g_value_set_uint(value, amglue_SvU32(sv));
- break;
+ return TRUE;
case G_TYPE_LONG:
g_value_set_int64(value, amglue_SvI64(sv));
- break;
+ return TRUE;
case G_TYPE_ULONG:
g_value_set_uint64(value, amglue_SvU64(sv));
- break;
+ return TRUE;
case G_TYPE_INT64:
g_value_set_int64(value, amglue_SvI64(sv));
- break;
+ return TRUE;
case G_TYPE_UINT64:
g_value_set_uint64(value, amglue_SvU64(sv));
- break;
+ return TRUE;
case G_TYPE_FLOAT:
- if (!SvNOK(sv)) return FALSE;
g_value_set_float(value, SvNV(sv));
- break;
+ return TRUE;
case G_TYPE_DOUBLE:
- if (!SvNOK(sv)) return FALSE;
g_value_set_double(value, SvNV(sv));
- break;
-
- case G_TYPE_STRING:
- if (!SvPOK(sv)) return FALSE;
- g_value_set_string(value, SvPV_nolen(sv));
- break;
+ return TRUE;
- case G_TYPE_ENUM:
- if (!SvIOK(sv)) return FALSE;
+ case G_TYPE_ENUM:
g_value_set_enum(value, SvIV(sv));
- break;
+ return TRUE;
case G_TYPE_FLAGS:
- if (!SvIOK(sv)) return FALSE;
g_value_set_flags(value, SvIV(sv));
- break;
+ return TRUE;
- /* Unsupported */
default:
- case G_TYPE_POINTER:
- case G_TYPE_INTERFACE:
- case G_TYPE_BOXED: /* note: *getting* boxed values is supported */
- case G_TYPE_OBJECT:
- case G_TYPE_PARAM:
- return FALSE;
+ /* for anything else, let perl stringify it for us and try parsing it */
+ return g_value_set_from_string(value, SvPV_nolen(sv));
}
-
- return TRUE;
}
%}
/*
- * Device struct, %extend-ed into a Perl class
+ * DirectTCPConnection object
*/
-typedef struct queue_fd_t {
- /* Instance variables -- all readonly */
- %immutable;
- int fd;
- char *errmsg;
-
- %mutable;
-
- /* methods */
+typedef struct DirectTCPConnection {
%extend {
- /* constructor */
- queue_fd_t(int fd) {
- return queue_fd_new(fd, NULL);
- }
+ ~DirectTCPConnection() {
+ g_object_unref(self);
+ };
- /* destructor */
- ~queue_fd_t() {
- amfree(self->errmsg);
- g_free(self);
+ %newobject close;
+ char *close() {
+ return directtcp_connection_close(self);
}
- }
-} queue_fd_t;
+ };
+} DirectTCPConnection;
+
+/*
+ * Device struct, %extend-ed into a Perl class
+ */
+
+%name(unaliased_name) extern char *device_unaliased_name(char *);
typedef struct Device {
return device_write_block(self, size, data);
}
- gboolean
- write_from_fd(queue_fd_t *queue_fd) {
- return device_write_from_fd(self, queue_fd);
- }
-
gboolean
finish_file() {
return device_finish_file(self);
return device_read_block(self, buffer, size);
}
- gboolean read_to_fd(queue_fd_t *queue_fd) {
- return device_read_to_fd(self, queue_fd);
+ gboolean
+ erase() {
+ return device_erase(self);
+ }
+
+ gboolean
+ eject() {
+ return device_eject(self);
+ }
+
+ gboolean
+ directtcp_supported() {
+ return device_directtcp_supported(self);
+ }
+
+ void
+ listen(gboolean for_writing, DirectTCPAddr **addrs) {
+ /* ensure that the addresses are empty if there was an error */
+ if (!device_listen(self, for_writing, addrs))
+ *addrs = NULL;
+ }
+
+ %newobject accept; /* connection is already ref'd, so we own it */
+ DirectTCPConnection *
+ accept() {
+ DirectTCPConnection *conn = NULL;
+ gboolean rv;
+
+ rv = device_accept(self, &conn, NULL, NULL);
+ if (!rv && conn) {
+ /* conn is ref'd for our convenience, but we don't want it */
+ g_object_unref(conn);
+ conn = NULL;
+ }
+ return conn;
+ }
+
+ %newobject connect; /* connection is already ref'd, so we own it */
+ DirectTCPConnection *
+ connect(gboolean for_writing, DirectTCPAddr *addrs) {
+ DirectTCPConnection *conn = NULL;
+ gboolean rv;
+
+ rv = device_connect(self, for_writing, addrs, &conn, NULL, NULL);
+ if (!rv && conn) {
+ /* conn is ref'd for our convenience, but we don't want it */
+ g_object_unref(conn);
+ conn = NULL;
+ }
+ return conn;
+ }
+
+ gboolean
+ use_connection(DirectTCPConnection *conn) {
+ return device_use_connection(self, conn);
+ }
+
+ %typemap(in,numinputs=0) guint64 *actual_size (guint64 sz) {
+ sz = 0;
+ $1 = &sz;
+ }
+ %typemap(argout) guint64 *actual_size {
+ SP += argvi; PUTBACK;
+ $result = sv_2mortal(amglue_newSVu64(*$1));
+ SPAGAIN; SP -= argvi; argvi++;
+ }
+ gboolean
+ write_from_connection(guint64 size, guint64 *actual_size) {
+ return device_write_from_connection(self, size, actual_size);
+ }
+
+ gboolean
+ read_to_connection(guint64 size, guint64 *actual_size) {
+ return device_read_to_connection(self, size, actual_size);
}
%typemap(out) const GSList * {
if (SvPOK($input))
pname = SvPV_nolen($input);
- if (pname) $1 = (DevicePropertyBase *)device_property_get_by_name(pname);
- if (!pname || !$1) {
- SWIG_exception_fail(SWIG_ValueError, "Invalid property name");
- }
+ if (pname)
+ $1 = (DevicePropertyBase *)device_property_get_by_name(pname);
+ else
+ $1 = NULL;
}
/* A typemap to convert the GValue in property_get to a return value. The
PropertySource *source, gboolean *val_found) {
/* if the result is valid */
if (*$4) {
- /* move data from $1 to $result, somehow */
+ /* move data from $1 to $result, somehow, being careful to
+ * save the perl stack while doing so */
+ SP += argvi; PUTBACK;
$result = set_sv_from_gvalue($1);
- argvi++;
+ SPAGAIN; SP -= argvi; argvi++;
/* free any memory for the GValue */
g_value_unset($1);
void
property_get(DevicePropertyBase *pbase, GValue *out_val, PropertySurety *surety,
PropertySource *source, gboolean *val_found) {
- *val_found = device_property_get_ex(self, pbase->ID, out_val, surety, source);
+ if (pbase) {
+ *val_found = device_property_get_ex(self, pbase->ID, out_val, surety, source);
+ } else {
+ *val_found = FALSE;
+ }
}
/* delete typemaps */
gboolean
property_set(DevicePropertyBase *pbase, SV *sv) {
GValue gval;
+
+ if (!pbase)
+ goto fail;
memset(&gval, 0, sizeof(gval));
g_value_init(&gval, pbase->type);
if (!set_gvalue_from_sv(sv, &gval))
gboolean recycle_file(guint filenum) {
return device_recycle_file(self, filenum);
}
-
+
/* accessor functions */
int file(void) { return self->file; }
char * device_name(void) { return self->device_name; }
DeviceAccessMode access_mode(void) { return self->access_mode; }
gboolean is_eof(void) { return self->is_eof; }
+ gboolean is_eom(void) { return self->is_eom; }
char * volume_label(void) { return self->volume_label; }
char * volume_time(void) { return self->volume_time; }
DeviceStatusFlags status(void) { return self->status; }
} Device;
+/* An alternate constructor for RAIT devices */
+%typemap(in) GSList *child_devices {
+ AV *av;
+ int i, len;
+
+ if (!SvROK($input) || SvTYPE(SvRV($input)) != SVt_PVAV) {
+ SWIG_exception(SWIG_TypeError, "Expected an arrayref");
+ }
+ av = (AV *)SvRV($input);
+
+ $1 = NULL;
+ len = av_len(av);
+ for (i = 0; i <= len; i++) {
+ SV **elt = av_fetch(av, i, 0);
+ Device *d;
+
+ if (elt && !SvOK(*elt)) {
+ $1 = g_slist_append($1, NULL); /* 'undef' => NULL */
+ } else if (!elt || SWIG_ConvertPtr(*elt, (void **)&d, $descriptor(Device *), 0) == -1) {
+ SWIG_exception(SWIG_TypeError, "array member is not a Device");
+ } else {
+ $1 = g_slist_append($1, d);
+ }
+ }
+}
+%typemap(freearg) GSList *child_devices {
+ g_slist_free($1);
+}
+%newobject rait_device_open_from_children;
+Device *rait_device_open_from_children(GSList *child_devices);
+%perlcode %{
+sub new_rait_from_children {
+ my $class = shift; # strip the $class from the arguments
+ return rait_device_open_from_children([@_]);
+}
+%}
+
+/*
+ * Utilities for installchecks (not described in POD)
+ */
+
+%inline %{
+
+/* write LENGTH bytes of random data to FILENAME, seeded with SEED */
+gboolean
+write_random_to_device(guint32 seed, size_t length, Device *device) {
+ simpleprng_state_t prng;
+ char *buf;
+ gsize block_size = device->block_size;
+ g_assert(block_size < G_MAXUINT);
+
+ buf = g_malloc(block_size);
+ simpleprng_seed(&prng, seed);
+
+ while (length) {
+ size_t to_write = min(block_size, length);
+
+ simpleprng_fill_buffer(&prng, buf, to_write);
+ if (!device_write_block(device, (guint)block_size, buf)) {
+ g_free(buf);
+ return FALSE;
+ }
+ length -= to_write;
+ }
+
+ g_free(buf);
+ return TRUE;
+}
+
+/* read LENGTH bytes of random data from FILENAME verifying it against
+ * a PRNG seeded with SEED. Sends any error messages to stderr.
+ */
+gboolean
+verify_random_from_device(guint32 seed, size_t length, Device *device) {
+ simpleprng_state_t prng;
+ char *buf = NULL; /* first device_read_block will get the size */
+ int block_size = 0;
+
+ simpleprng_seed(&prng, seed);
+
+ while (length) {
+ int bytes_read;
+ int size = block_size;
+
+ bytes_read = device_read_block(device, buf, &size);
+ if (bytes_read == 0 && size > block_size) {
+ g_free(buf);
+ block_size = size;
+ buf = g_malloc(block_size);
+ continue;
+ }
+ if (bytes_read == -1) {
+ if (device->status == DEVICE_STATUS_SUCCESS) {
+ g_assert(device->is_eof);
+ g_debug("verify_random_from_device got unexpected EOF");
+ }
+ goto error;
+ }
+
+ /* strip padding */
+ bytes_read = min(bytes_read, length);
+
+ if (!simpleprng_verify_buffer(&prng, buf, bytes_read))
+ goto error;
+
+ length -= bytes_read;
+ }
+
+ g_free(buf);
+ return TRUE;
+
+error:
+ g_free(buf);
+ return FALSE;
+}
+%}
+
/*
* Constants
*/
amglue_copy_to_tag(PropertyPhaseFlags, constants);
amglue_add_flag_tag_fns(PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_GET_BEFORE_START,
+amglue_add_constant_short(PROPERTY_ACCESS_GET_BEFORE_START,
"GET_BEFORE_START", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_GET_BETWEEN_FILE_WRITE,
+amglue_add_constant_short(PROPERTY_ACCESS_GET_BETWEEN_FILE_WRITE,
"GET_BETWEEN_FILE_WRITE", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_GET_INSIDE_FILE_WRITE,
+amglue_add_constant_short(PROPERTY_ACCESS_GET_INSIDE_FILE_WRITE,
"GET_INSIDE_FILE_WRITE", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_GET_BETWEEN_FILE_READ,
+amglue_add_constant_short(PROPERTY_ACCESS_GET_BETWEEN_FILE_READ,
"GET_BETWEEN_FILE_READ", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_GET_INSIDE_FILE_READ,
+amglue_add_constant_short(PROPERTY_ACCESS_GET_INSIDE_FILE_READ,
"GET_INSIDE_FILE_READ", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_SET_BEFORE_START,
+amglue_add_constant_short(PROPERTY_ACCESS_SET_BEFORE_START,
"SET_BEFORE_START", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_SET_BETWEEN_FILE_WRITE,
+amglue_add_constant_short(PROPERTY_ACCESS_SET_BETWEEN_FILE_WRITE,
"SET_BETWEEN_FILE_WRITE", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_SET_INSIDE_FILE_WRITE,
+amglue_add_constant_short(PROPERTY_ACCESS_SET_INSIDE_FILE_WRITE,
"SET_INSIDE_FILE_WRITE", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_SET_BETWEEN_FILE_READ,
+amglue_add_constant_short(PROPERTY_ACCESS_SET_BETWEEN_FILE_READ,
"SET_BETWEEN_FILE_READ", PropertyAccessFlags);
-amglue_add_constant_short(PROPERTY_ACCESS_SET_INSIDE_FILE_READ,
+amglue_add_constant_short(PROPERTY_ACCESS_SET_INSIDE_FILE_READ,
"SET_INSIDE_FILE_READ", PropertyAccessFlags);
amglue_add_constant_noshort(PROPERTY_ACCESS_GET_MASK, PropertyAccessFlags);
amglue_add_constant_noshort(PROPERTY_ACCESS_SET_MASK, PropertyAccessFlags);
amglue_add_constant_short(MEDIA_ACCESS_MODE_WRITE_ONLY, "WRITE_ONLY", MediaAccessMode);
amglue_copy_to_tag(MediaAccessMode, constants);
-amglue_add_enum_tag_fns(SizeAccuracy);
-amglue_add_constant_short(SIZE_ACCURACY_UNKNOWN, "UNKNOWN", SizeAccuracy);
-amglue_add_constant_short(SIZE_ACCURACY_ESTIMATE, "ESTIMATE", SizeAccuracy);
-amglue_add_constant_short(SIZE_ACCURACY_REAL, "REAL", SizeAccuracy);
-amglue_copy_to_tag(SizeAccuracy, constants);
-
amglue_add_flag_tag_fns(PropertySurety);
amglue_add_constant_short(PROPERTY_SURETY_BAD, "SURETY_BAD", PropertySurety);
amglue_add_constant_short(PROPERTY_SURETY_GOOD, "SURETY_GOOD", PropertySurety);
%perlcode %{
-# SWIG produces a sub-package for the Device "class", in this case named
+# SWIG produces a sub-package for the Device "class", in this case named
# Amanda::Device::Device. For user convenience, we allow Amanda::Device->new(..) to
# do the same thing. This is a wrapper function, and not just a typeglob assignment,
# because we want to get the right blessing.