Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mkstemp() and fdopen() in Cygwin 1.7.28

Tags:

c++

c

gcc

cygwin

In the course of building some C++-based code under Cygwin (1.7.28-2, 64-bit) with GNU GCC 4.8.2, I ran into the following errors:

...
SortDetails.cpp: In function ‘FILE* create_tmpfile(const char*, char**)’:
SortDetails.cpp:127:20: error: ‘mkstemp’ was not declared in this scope
   fd = mkstemp(tmpl);
                    ^
SortDetails.cpp:133:24: error: ‘fdopen’ was not declared in this scope
   fp = fdopen(fd, "wb+");
...

The specific chunk of code that fails to compile is:

FILE *
create_tmpfile(char const* path, char** fileName)
{
  FILE* fp;
  int fd;
  char* tmpl;

  if ( path == NULL )
      {
          fileName = NULL;
          return tmpfile();
      }

  tmpl = (char*)malloc(1 + strlen(path) + L_tmpnam);
  strcpy(tmpl, path);
  strcpy(tmpl+strlen(path), "/sb.XXXXXX");
  fd = mkstemp(tmpl);                        /* <----- here... */
  if(fd == -1)
      {
          fprintf(stderr, "unable to create temp file!\n");
          return NULL;
      }
  fp = fdopen(fd, "wb+");                    /* <----- ...and here */
  *fileName = (char*)malloc(strlen(tmpl) + 1);
  strcpy(*fileName, tmpl);
  free(tmpl);
  return fp;
}

(The results of malloc are being cast because this code is within a larger C++-based project.)

Regression

This code compiles and works successfully with GNU GCC 4.8.x on Linux hosts and with Clang/++ 5.0 under OS X.

Environment

I am using the following version of Cygwin:

$ uname -a
CYGWIN_NT-6.1 CygFoo-PC 1.7.28(0.271/5/3) 2014-02-09 21:06 x86_64 Cygwin

Here is the version of GCC I am using:

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-pc-cygwin/4.8.2/lto-wrapper.exe
Target: x86_64-pc-cygwin
Configured with: /cygdrive/i/szsz/tmpp/cygwin64/gcc/gcc-4.8.2-2/src/gcc-4.8.2/configure --srcdir=/cygdrive/i/szsz/tmpp/cygwin64/gcc/gcc-4.8.2-2/src/gcc-4.8.2 --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdir=/usr/libexec --datadir=/usr/share --localstatedir=/var --sysconfdir=/etc --libdir=/usr/lib --datarootdir=/usr/share --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --disable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libgomp --disable-libitm --enable-libquadmath --enable-math-support --enable-libssp --enable-libada --enable-libgcj-sublibs --disable-java-awt --disable-symvers --with-ecj-jar=/usr/share/java/ecj.jar --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib
Thread model: posix
gcc version 4.8.2 (GCC)

Questions

  1. Is there support for mkstemp() and fdopen() in GCC 4.8.2 for Cygwin?

  2. If not, is there a package I can add or a library I can relatively easily compile to add support for these functions?

  3. If not, are there alternatives to mkstemp() and fdopen() that I can make use of to replicate their functionality under Cygwin?

Possible fix

Here's a modified version of this function:

FILE *
create_tmpfile(char const* path, char** fileName)
{
  FILE* fp;
  char* tmpl;

  if ( path == NULL )
      {
          fileName = NULL;
          return tmpfile();
      }

#if defined(__CYGWIN__) && !defined(_WIN32)
  const char *cygwinPrefix = "/sb.";
  const char *cygwinTmpDir = "/tmp";
  char *cygwinTmplSuffix = (char *)malloc(1 + L_tmpnam);
  tmpnam(cygwinTmplSuffix);
  tmpl = (char *)malloc(1 + strlen(path) + strlen(cygwinPrefix) + strlen(cygwinTmplSuffix + strlen(cygwinTmpDir) + 1));
  strcpy(tmpl, path);
  strcpy(tmpl+strlen(path), cygwinPrefix);
  strcpy(tmpl+strlen(path)+strlen(cygwinPrefix), cygwinTmplSuffix + strlen(cygwinTmpDir) + 1);
  fp = fopen(tmpl, "wbx+"); /* we add the 'x' extension to apply the O_EXCL flag, to avoid a security hole described in the GNU C library docs */
  free(cygwinTmplSuffix);
#else
  tmpl = (char*)malloc(1 + strlen(path) + L_tmpnam);
  strcpy(tmpl, path);
  strcpy(tmpl+strlen(path), "/sb.XXXXXX");
  int fd = mkstemp(tmpl);
  if(fd == -1)
      {
          fprintf(stderr, "unable to create temp file!\n");
          return NULL;
      }
  fp = fdopen(fd, "wb+");
#endif
  *fileName = (char*)malloc(strlen(tmpl) + 1);
  strcpy(*fileName, tmpl);
  free(tmpl);
  return fp;
}

This is pretty ugly. If there is a way to use POSIX functions, I'd like to use them, if I can. Thanks for any advice.

like image 779
Alex Reynolds Avatar asked Feb 10 '14 22:02

Alex Reynolds


2 Answers

When compiling with g++ 4.8.2 on Cygwin, I logged the expansion of macros in three cases:

$ g++ -std=c++11 -E -Dd foo.cpp > foo.log.c++11
$ g++ -ansi -E -Dd foo.cpp > foo.log.ansi
$ g++ -E -Dd foo.cpp > foo.log.noFlag

Comparing the logs was useful. There were "holes" in the -std=c++11 and -ansi cases, while a block containing the mkstemp() declaration shows up in the "flagless" case. This let me zero in on the parts of the headers that were processed differently.

In the file /usr/include/stdlib.h, declarations of mkstemp() and some other functions are rejected if __STRICT_ANSI__ is defined — such as when we use the compile-time flags -ansi and -std=c++11.

Likewise, in the file /usr/include/stdio.h, declarations of fdopen() will get skipped for the same reason.

The C++ headers <cstdlib> and <cstdio> both include the stdlib.h and stdio.h headers and leave declaration of those two functions (among others) up to those two headers. So if we use -ansi and/or -std=c++11 then those two functions will not be declared and we get the compile errors.

The solution that seems to work for the toy code samples is to undefine __STRICT_ANSI__ before compiling:

$ g++ -std=c++11 -U__STRICT_ANSI__ foo.cpp

It's not clear what the side effects of this will be, but from googling, it seems like this is a common problem and a common fix applied by other developers who need to target Cygwin.

like image 116
Alex Reynolds Avatar answered Oct 10 '22 22:10

Alex Reynolds


Cygwin has a Linux-like set of Feature Test Macros. However, on Linux with C++, _GNU_SOURCE is defined unconditionally, essentially negating all such guards. On Cygwin, we do not do this, meaning you actually have to respect the meaning of the various flags on C++ as well.

As noted, using any -std=c++* flag will define __STRICT_ANSI__, which is recognized by the macros. Undefining that on the command line is incorrect. Instead, either define the correct documented flag(s) for the functions you wish to use (in this case, -D_POSIX_C_SOURCE=200809L should cover both), or use -std=gnu++* flags instead (which, btw, do not define _GNU_SOURCE) to not declare ANSI compliance.

like image 30
Yaakov Avatar answered Oct 10 '22 23:10

Yaakov