On Mon, Oct 15, 2012 at 07:53:51AM -0400, Noah Misch wrote:
> The only matter still requiring attention is a fix for IsoLocaleName().

Following off-list coordination with Brar, I went about finishing up this
patch.  The above problem proved deeper than expected.  For Windows Vista,
Microsoft made RFC 4646 tags the preferred way to denote a locale in Windows.
Microsoft calls them "locale names".  Starting with Visual Studio 2012,
setlocale() accepts locale names in addition to all the things it previously
accepted.  One can now use things like "initdb --locale=zh-CN" and "CREATE
DATABASE foo LC_CTYPE = 'pl'".  This meant updating win32_langinfo() and
find_matching_ts_config() to handle the new formats.  In passing, I fixed an
unchecked malloc() in win32_langinfo().

In addition to expanding the set of valid locale inputs, VS2012 changes the
(undocumented) content of _locale_t to hold locale names where it previously
held locale identifiers.  I taught IsoLocaleName() to handle the new material.
I also sought to improve the comments on IsoLocaleName(); its significance was
not previously clear to me.  This thread has some background:
http://archives.postgresql.org/message-id/4964b45e.5080...@hagander.net

Though I'm not entirely sanguine about digging into the officially-opaque
_locale_t, we have been doing it that way for several years.  None of the
alternatives are clearly-satisfying.  In particular, I found no good way to
look up the code page corresponding to a locale name on pre-Vista systems.
The CRT itself handles this by translating locale names to locale identifiers
using a lookup table.  The Gnulib "localename" and "setlocale" modules are
also interesting studies on the topic.

In previous reviews, I missed the need to update pgwin32_putenv().  The
addition of VS2010 support had also missed it, so this catches up.  That
function has other problems, but I will hold them for another patch.

Tester warning: if you currently have some form of VS2010 installed, including
the compilers of Windows SDK 7.1, beware of this problem:
http://stackoverflow.com/questions/10888391/link-fatal-error-lnk1123-failure-during-conversion-to-coff-file-invalid-or-c

Thanks,
nm
*** a/doc/src/sgml/install-windows.sgml
--- b/doc/src/sgml/install-windows.sgml
***************
*** 19,26 ****
   <para>
    There are several different ways of building PostgreSQL on
    <productname>Windows</productname>. The simplest way to build with
!   Microsoft tools is to install a supported version of the
!   <productname>Microsoft Windows SDK</productname> and use the included
    compiler. It is also possible to build with the full
    <productname>Microsoft Visual C++ 2005, 2008 or 2010</productname>. In some 
cases
    that requires the installation of the <productname>Windows SDK</productname>
--- 19,26 ----
   <para>
    There are several different ways of building PostgreSQL on
    <productname>Windows</productname>. The simplest way to build with
!   Microsoft tools is to install <productname>Visual Studio Express 2012
!   for Windows Desktop</productname> and use the included
    compiler. It is also possible to build with the full
    <productname>Microsoft Visual C++ 2005, 2008 or 2010</productname>. In some 
cases
    that requires the installation of the <productname>Windows SDK</productname>
***************
*** 77,93 ****
    <productname>Visual Studio Express</productname> or some versions of the
    <productname>Microsoft Windows SDK</productname>. If you do not already 
have a
    <productname>Visual Studio</productname> environment set up, the easiest
!   way is to use the compilers in the <productname>Windows SDK</productname>,
!   which is a free download from Microsoft.
   </para>
  
   <para>
    PostgreSQL is known to support compilation using the compilers shipped with
    <productname>Visual Studio 2005</productname> to
!   <productname>Visual Studio 2010</productname> (including Express editions),
    as well as standalone Windows SDK releases 6.0 to 7.1.
    64-bit PostgreSQL builds are only supported with
!   <productname>Microsoft Windows SDK</productname> version 6.0a and above or
    <productname>Visual Studio 2008</productname> and above.
   </para>
  
--- 77,94 ----
    <productname>Visual Studio Express</productname> or some versions of the
    <productname>Microsoft Windows SDK</productname>. If you do not already 
have a
    <productname>Visual Studio</productname> environment set up, the easiest
!   ways are to use the compilers in the <productname>Windows SDK 
7.1</productname>
!   or those from <productname>Visual Studio Express 2012 for Windows
!   Desktop</productname>, which are both free downloads from Microsoft.
   </para>
  
   <para>
    PostgreSQL is known to support compilation using the compilers shipped with
    <productname>Visual Studio 2005</productname> to
!   <productname>Visual Studio 2012</productname> (including Express editions),
    as well as standalone Windows SDK releases 6.0 to 7.1.
    64-bit PostgreSQL builds are only supported with
!   <productname>Microsoft Windows SDK</productname> version 6.0a to 7.1 or
    <productname>Visual Studio 2008</productname> and above.
   </para>
  
***************
*** 146,162 **** $ENV{PATH}=$ENV{PATH} . ';c:\some\where\bison\bin';
      <varlistentry>
       <term><productname>Microsoft Windows SDK</productname></term>
       <listitem><para>
!       It is recommended that you upgrade to the latest supported version
!       of the <productname>Microsoft Windows SDK</productname> (currently
        version 7.1), available for download from
        <ulink url="http://www.microsoft.com/downloads/";></>.
       </para>
       <para>
        You must always include the
        <application>Windows Headers and Libraries</application> part of the 
SDK.
!       If you install the <productname>Windows SDK</productname>
        including the <application>Visual C++ Compilers</application>,
        you don't need <productname>Visual Studio</productname> to build.
       </para></listitem>
      </varlistentry>
  
--- 147,166 ----
      <varlistentry>
       <term><productname>Microsoft Windows SDK</productname></term>
       <listitem><para>
!       If your build environment doesn't ship with a supported version of the
!       <productname>Microsoft Windows SDK</productname> it
!       is recommended that you upgrade to the latest version (currently
        version 7.1), available for download from
        <ulink url="http://www.microsoft.com/downloads/";></>.
       </para>
       <para>
        You must always include the
        <application>Windows Headers and Libraries</application> part of the 
SDK.
!       If you install a <productname>Windows SDK</productname>
        including the <application>Visual C++ Compilers</application>,
        you don't need <productname>Visual Studio</productname> to build.
+       Note that as of Version 8.0a the Windows SDK no longer ships with a
+       complete command-line build environment.
       </para></listitem>
      </varlistentry>
  
***************
*** 198,207 **** $ENV{PATH}=$ENV{PATH} . ';c:\some\where\bison\bin';
        Bison can be downloaded from <ulink 
url="http://gnuwin32.sourceforge.net";></>.
        Flex can be downloaded from
        <ulink url="http://www.postgresql.org/ftp/misc/winflex/";></>.
!       If you are using <productname>msysGit</productname> for accessing the
!       PostgreSQL <productname>Git</productname> repository you probably 
already
!       have recent versions of bison and flex in your 
<productname>Git</productname>
!       binary directory.
       </para>
  
       <note>
--- 202,211 ----
        Bison can be downloaded from <ulink 
url="http://gnuwin32.sourceforge.net";></>.
        Flex can be downloaded from
        <ulink url="http://www.postgresql.org/ftp/misc/winflex/";></>.
!       If you are using <productname>msysGit</productname> or 
<productname>GitHub for
!       Windows</productname> for accessing the PostgreSQL 
<productname>Git</productname>
!       repository you probably already have recent versions of bison and flex 
in your
!       <productname>Git</productname> binary directory.
       </para>
  
       <note>
*** a/src/backend/utils/adt/pg_locale.c
--- b/src/backend/utils/adt/pg_locale.c
***************
*** 715,726 **** cache_locale_time(void)
  
  #if defined(WIN32) && defined(LC_MESSAGES)
  /*
!  *    Convert Windows locale name to the ISO formatted one
!  *    if possible.
   *
!  *    This function returns NULL if conversion is impossible,
!  *    otherwise returns the pointer to a static area which
!  *    contains the iso formatted locale name.
   */
  static char *
  IsoLocaleName(const char *winlocname)
--- 715,755 ----
  
  #if defined(WIN32) && defined(LC_MESSAGES)
  /*
!  * Convert a Windows setlocale() argument to a Unix-style one.
   *
!  * Regardless of platform, we install message catalogs under a Unix-style
!  * LL[_CC][.ENCODING][@VARIANT] naming convention.  Only LC_MESSAGES settings
!  * following that style will elicit localized interface strings.
!  *
!  * Before Visual Studio 2012 (msvcr110.dll), Windows setlocale() accepted "C"
!  * (but not "c") and strings of the form <Language>[_<Country>][.<CodePage>],
!  * case-insensitive.  setlocale() returns the fully-qualified form; for
!  * example, setlocale("thaI") returns "Thai_Thailand.874".  Internally,
!  * setlocale() and _create_locale() select a "locale identifier"[1] and store
!  * it in an undocumented _locale_t field.  From that LCID, we can retrieve the
!  * ISO 639 language and the ISO 3166 country.  Character encoding does not
!  * matter, because the server and client encodings govern that.
!  *
!  * Windows Vista introduced the "locale name" concept[2], closely following
!  * RFC 4646.  Locale identifiers are now deprecated.  Starting with Visual
!  * Studio 2012, setlocale() accepts locale names in addition to the strings it
!  * accepted historically.  It does not standardize them; setlocale("Th-tH")
!  * returns "Th-tH".  setlocale(category, "") still returns a traditional
!  * string.  Furthermore, msvcr110.dll changed the undocumented _locale_t
!  * content to carry locale names instead of locale identifiers.
!  *
!  * MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol.
!  * IsoLocaleName() always fails in a MinGW-built postgres.exe, so only
!  * Unix-style values of the lc_messages GUC can elicit localized messages.  In
!  * particular, every lc_messages setting that initdb can select automatically
!  * will yield only C-locale messages.  XXX This could be fixed by running the
!  * fully-qualified locale name through a lookup table.
!  *
!  * This function returns a pointer to a static buffer bearing the converted
!  * name or NULL if conversion fails.
!  *
!  * [1] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373763.aspx
!  * [2] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373814.aspx
   */
  static char *
  IsoLocaleName(const char *winlocname)
***************
*** 739,744 **** IsoLocaleName(const char *winlocname)
--- 768,801 ----
        loct = _create_locale(LC_CTYPE, winlocname);
        if (loct != NULL)
        {
+ #if (_MSC_VER >= 1700)                        /* Visual Studio 2012 or later 
*/
+               size_t          rc;
+               char       *hyphen;
+ 
+               /* Locale names use only ASCII, any conversion locale suffices. 
*/
+               rc = wchar2char(iso_lc_messages, 
loct->locinfo->locale_name[LC_CTYPE],
+                                               sizeof(iso_lc_messages), NULL);
+               _free_locale(loct);
+               if (rc == -1 || rc == sizeof(iso_lc_messages))
+                       return NULL;
+ 
+               /*
+                * Since the message catalogs sit on a case-insensitive 
filesystem, we
+                * need not standardize letter case here.  So long as we do not 
ship
+                * message catalogs for which it would matter, we also need not
+                * translate the script/variant portion, e.g. uz-Cyrl-UZ to
+                * uz_UZ@cyrillic.  Simply replace the hyphen with an 
underscore.
+                *
+                * Note that the locale name can be less-specific than the 
value we
+                * would derive under earlier Visual Studio releases.  For 
example,
+                * French_France.1252 yields just "fr".  This does not affect 
any of
+                * the country-specific message catalogs available as of this 
writing
+                * (pt_BR, zh_CN, zh_TW).
+                */
+               hyphen = strchr(iso_lc_messages, '-');
+               if (hyphen)
+                       *hyphen = '_';
+ #else
                char            isolang[32],
                                        isocrty[32];
                LCID            lcid;
***************
*** 753,758 **** IsoLocaleName(const char *winlocname)
--- 810,816 ----
                if (!GetLocaleInfoA(lcid, LOCALE_SISO3166CTRYNAME, isocrty, 
sizeof(isocrty)))
                        return NULL;
                snprintf(iso_lc_messages, sizeof(iso_lc_messages) - 1, "%s_%s", 
isolang, isocrty);
+ #endif
                return iso_lc_messages;
        }
        return NULL;
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
***************
*** 923,936 **** find_matching_ts_config(const char *lc_type)
  
        /*
         * Convert lc_ctype to a language name by stripping everything after an
!        * underscore.  Just for paranoia, we also stop at '.' or '@'.
         */
        if (lc_type == NULL)
                langname = pg_strdup("");
        else
        {
                ptr = langname = pg_strdup(lc_type);
!               while (*ptr && *ptr != '_' && *ptr != '.' && *ptr != '@')
                        ptr++;
                *ptr = '\0';
        }
--- 923,944 ----
  
        /*
         * Convert lc_ctype to a language name by stripping everything after an
!        * underscore (usual case) or a hyphen (Windows "locale name"; see
!        * comments at IsoLocaleName()).
!        *
!        * XXX Should ' ' be a stop character?  This would select "norwegian" 
for
!        * the Windows locale "Norwegian (Nynorsk)_Norway.1252".  If we do so, 
we
!        * should also accept the "nn" and "nb" Unix locales.
!        *
!        * Just for paranoia, we also stop at '.' or '@'.
         */
        if (lc_type == NULL)
                langname = pg_strdup("");
        else
        {
                ptr = langname = pg_strdup(lc_type);
!               while (*ptr &&
!                          *ptr != '_' && *ptr != '-' && *ptr != '.' && *ptr != 
'@')
                        ptr++;
                *ptr = '\0';
        }
*** a/src/port/chklocale.c
--- b/src/port/chklocale.c
***************
*** 189,214 **** static const struct encoding_match encoding_match_list[] = {
  
  #ifdef WIN32
  /*
!  * On Windows, use CP<codepage number> instead of the nl_langinfo() result
   */
  static char *
  win32_langinfo(const char *ctype)
  {
!       char       *r;
        char       *codepage;
-       int                     ln;
  
        /*
         * Locale format on Win32 is <Language>_<Country>.<CodePage> . For
!        * example, English_USA.1252.
         */
        codepage = strrchr(ctype, '.');
!       if (!codepage)
!               return NULL;
!       codepage++;
!       ln = strlen(codepage);
!       r = malloc(ln + 3);
!       sprintf(r, "CP%s", codepage);
  
        return r;
  }
--- 189,237 ----
  
  #ifdef WIN32
  /*
!  * On Windows, use CP<code page number> instead of the nl_langinfo() result
!  *
!  * Visual Studio 2012 expanded the set of valid LC_CTYPE values, so have its
!  * locale machinery determine the code page.  See comments at IsoLocaleName().
!  * For other compilers, follow the locale's predictable format.
!  *
!  * Returns a malloc()'d string for the caller to free.
   */
  static char *
  win32_langinfo(const char *ctype)
  {
!       char       *r = NULL;
! 
! #if (_MSC_VER >= 1700)
!       _locale_t       loct = NULL;
! 
!       loct = _create_locale(LC_CTYPE, ctype);
!       if (loct != NULL)
!       {
!               r = malloc(16);                 /* excess */
!               if (r != NULL)
!                       sprintf(r, "CP%u", loct->locinfo->lc_codepage);
!               _free_locale(loct);
!       }
! #else
        char       *codepage;
  
        /*
         * Locale format on Win32 is <Language>_<Country>.<CodePage> . For
!        * example, English_United States.1252.
         */
        codepage = strrchr(ctype, '.');
!       if (codepage != NULL)
!       {
!               int                     ln;
! 
!               codepage++;
!               ln = strlen(codepage);
!               r = malloc(ln + 3);
!               if (r != NULL)
!                       sprintf(r, "CP%s", codepage);
!       }
! #endif
  
        return r;
  }
*** a/src/port/win32env.c
--- b/src/port/win32env.c
***************
*** 61,66 **** pgwin32_putenv(const char *envval)
--- 61,72 ----
                        "msvcr90", 0, NULL
                },                                              /* Visual 
Studio 2008 */
                {
+                       "msvcr100", 0, NULL
+               },                                              /* Visual 
Studio 2010 */
+               {
+                       "msvcr110", 0, NULL
+               },                                              /* Visual 
Studio 2012 */
+               {
                        NULL, 0, NULL
                }
        };
*** a/src/tools/msvc/MSBuildProject.pm
--- b/src/tools/msvc/MSBuildProject.pm
***************
*** 1,7 ****
  package MSBuildProject;
  
  #
! # Package that encapsulates a MSBuild (Visual C++ 2010) project file
  #
  # src/tools/msvc/MSBuildProject.pm
  #
--- 1,7 ----
  package MSBuildProject;
  
  #
! # Package that encapsulates a MSBuild project file (Visual C++ 2010 or 
greater)
  #
  # src/tools/msvc/MSBuildProject.pm
  #
***************
*** 397,400 **** sub new
--- 397,442 ----
        return $self;
  }
  
+ package VC2012Project;
+ 
+ #
+ # Package that encapsulates a Visual C++ 2012 project file
+ #
+ 
+ use strict;
+ use warnings;
+ use base qw(MSBuildProject);
+ 
+ sub new
+ {
+     my $classname = shift;
+     my $self = $classname->SUPER::_new(@_);
+     bless($self, $classname);
+ 
+     $self->{vcver} = '11.00';
+ 
+     return $self;
+ }
+ 
+ # This override adds the <PlatformToolset> element
+ # to the PropertyGroup labeled "Configuration"
+ sub WriteConfigurationPropertyGroup
+ {
+     my ($self, $f, $cfgname, $p) = @_;
+     my $cfgtype =
+       ($self->{type} eq "exe")
+       ?'Application'
+       :($self->{type} eq "dll"?'DynamicLibrary':'StaticLibrary');
+ 
+     print $f <<EOF;
+   <PropertyGroup 
Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'" 
Label="Configuration">
+     <ConfigurationType>$cfgtype</ConfigurationType>
+     <UseOfMfc>false</UseOfMfc>
+     <CharacterSet>MultiByte</CharacterSet>
+     <WholeProgramOptimization>$p->{wholeopt}</WholeProgramOptimization>
+     <PlatformToolset>v110</PlatformToolset>
+   </PropertyGroup>
+ EOF
+ }
+ 
  1;
*** a/src/tools/msvc/README
--- b/src/tools/msvc/README
***************
*** 92,101 **** These configuration arguments are passed over to 
Mkvcbuild::mkvcbuild
  (Mkvcbuild.pm) which creates the Visual Studio project and solution files.
  It does this by using VSObjectFactory::CreateSolution to create an object
  implementing the Solution interface (this could be either a VS2005Solution,
! a VS2008Solution or a VS2010Solution, all in Solution.pm, depending on the
! user's build environment) and adding objects implementing the corresponding
! Project interface (VC2005Project or VC2008Project from VCBuildProject.pm or
! VC2010Project from MSBuildProject.pm) to it.
  When Solution::Save is called, the implementations of Solution and Project
  save their content in the appropriate format.
  The final step of starting the appropriate build program (msbuild or vcbuild)
--- 92,102 ----
  (Mkvcbuild.pm) which creates the Visual Studio project and solution files.
  It does this by using VSObjectFactory::CreateSolution to create an object
  implementing the Solution interface (this could be either a VS2005Solution,
! a VS2008Solution, a VS2010Solution or a VS2012Solution, all in Solution.pm,
! depending on the user's build environment) and adding objects implementing
! the corresponding Project interface (VC2005Project or VC2008Project from
! VCBuildProject.pm or VC2010Project or VC2012Project from MSBuildProject.pm)
! to it.
  When Solution::Save is called, the implementations of Solution and Project
  save their content in the appropriate format.
  The final step of starting the appropriate build program (msbuild or vcbuild)
*** a/src/tools/msvc/Solution.pm
--- b/src/tools/msvc/Solution.pm
***************
*** 63,75 **** sub DeterminePlatform
  {
        my $self = shift;
  
!       # Determine if we are in 32 or 64-bit mode. Do this by seeing if CL has
!       # 64-bit only parameters.
        $self->{platform} = 'Win32';
!       open(P, "cl /? 2>NUL|") || die "cl command not found";
        while (<P>)
        {
!               if (/^\/favor:</)
                {
                        $self->{platform} = 'x64';
                        last;
--- 63,74 ----
  {
        my $self = shift;
  
!       # Examine CL help output to determine if we are in 32 or 64-bit mode.
        $self->{platform} = 'Win32';
!       open(P, "cl /? 2>&1 |") || die "cl command not found";
        while (<P>)
        {
!               if (/^\/favor:<.+AMD64/)
                {
                        $self->{platform} = 'x64';
                        last;
***************
*** 700,703 **** sub new
--- 699,726 ----
        return $self;
  }
  
+ package VS2012Solution;
+ 
+ #
+ # Package that encapsulates a Visual Studio 2012 solution file
+ #
+ 
+ use Carp;
+ use strict;
+ use warnings;
+ use base qw(Solution);
+ 
+ sub new
+ {
+     my $classname = shift;
+     my $self = $classname->SUPER::_new(@_);
+     bless($self, $classname);
+ 
+     $self->{solutionFileVersion} = '12.00';
+     $self->{vcver} = '11.00';
+     $self->{visualStudioName} = 'Visual Studio 2012';
+ 
+     return $self;
+ }
+ 
  1;
*** a/src/tools/msvc/VSObjectFactory.pm
--- b/src/tools/msvc/VSObjectFactory.pm
***************
*** 41,46 **** sub CreateSolution
--- 41,50 ----
        {
                return new VS2010Solution(@_);
        }
+       elsif ($visualStudioVersion eq '11.00')
+       {
+               return new VS2012Solution(@_);
+       }
        else
        {
                croak "The requested Visual Studio version is not supported.";
***************
*** 68,73 **** sub CreateProject
--- 72,81 ----
        {
                return new VC2010Project(@_);
        }
+       elsif ($visualStudioVersion eq '11.00')
+       {
+               return new VC2012Project(@_);
+       }
        else
        {
                croak "The requested Visual Studio version is not supported.";
***************
*** 82,88 **** sub DetermineVisualStudioVersion
        {
  
  # Determine version of nmake command, to set proper version of visual studio
! # we use nmake as it has existed for a long time and still exists in visual 
studio 2010
                open(P, "nmake /? 2>&1 |")
                  || croak
  "Unable to determine Visual Studio version: The nmake command wasn't found.";
--- 90,96 ----
        {
  
  # Determine version of nmake command, to set proper version of visual studio
! # we use nmake as it has existed for a long time and still exists in current 
visual studio versions
                open(P, "nmake /? 2>&1 |")
                  || croak
  "Unable to determine Visual Studio version: The nmake command wasn't found.";
***************
*** 107,117 **** sub DetermineVisualStudioVersion
  sub _GetVisualStudioVersion
  {
        my ($major, $minor) = @_;
!       if ($major > 10)
        {
                carp
  "The determined version of Visual Studio is newer than the latest supported 
version. Returning the latest supported version instead.";
!               return '10.00';
        }
        elsif ($major < 6)
        {
--- 115,125 ----
  sub _GetVisualStudioVersion
  {
        my ($major, $minor) = @_;
!       if ($major > 11)
        {
                carp
  "The determined version of Visual Studio is newer than the latest supported 
version. Returning the latest supported version instead.";
!               return '11.00';
        }
        elsif ($major < 6)
        {
*** a/src/tools/msvc/build.pl
--- b/src/tools/msvc/build.pl
***************
*** 50,56 **** elsif ($ARGV[0] ne "RELEASE")
  
  # ... and do it
  
! if ($buildwhat and $vcver eq '10.00')
  {
        system(
  "msbuild $buildwhat.vcxproj /verbosity:detailed /p:Configuration=$bconf");
--- 50,56 ----
  
  # ... and do it
  
! if ($buildwhat and $vcver >= 10.00)
  {
        system(
  "msbuild $buildwhat.vcxproj /verbosity:detailed /p:Configuration=$bconf");
*** a/src/tools/msvc/gendef.pl
--- b/src/tools/msvc/gendef.pl
***************
*** 40,45 **** while (<$ARGV[0]/*.obj>)
--- 40,46 ----
                next if $pieces[6] =~ /^\(/;
                next if $pieces[6] =~ /^__real/;
                next if $pieces[6] =~ /^__imp/;
+               next if $pieces[6] =~ /^__xmm/;
                next if $pieces[6] =~ /NULL_THUNK_DATA$/;
                next if $pieces[6] =~ /^__IMPORT_DESCRIPTOR/;
                next if $pieces[6] =~ /^__NULL_IMPORT/;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to