Date: Sun, 8 Mar 1998 14:42:43 +0200 (IST) From: Eli Zaretskii To: DJ Delorie cc: djgpp-workers AT delorie DOT com Subject: Temporary files considered unsafe Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Precedence: bulk A discussion on gnu.utils.bug caused me to look at how safe are our temporary files in a multi-processing environment (e.g., Windows or when the temporray directory is shared over a network). It turns out they are unsafe. (Maybe that's the reason we cannot compile in two different DOS boxes at the same time?) Here's a new function `_creatnew', and based on it, a change in `tmpfile' to make it safe. (Nate, please note that Borland has `creatnew' with similar but not identical interface.) *** /dev/null Fri Mar 6 19:23:35 1998 --- src/libc/dos/io/_creat_n.c Fri Mar 6 19:22:54 1998 *************** *** 0 **** --- 1,59 ---- + /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ + #include + #include + #include + #include + #include + #include + #include + #include + #include + + int + _creatnew(const char* filename, int attrib, int flags) + { + __dpmi_regs r; + int rv; + unsigned use_lfn = _USE_LFN; + + if (filename == 0) + { + errno = EINVAL; + return -1; + } + + if (__FSEXT_call_open_handlers(__FSEXT_creat, &rv, &filename)) + return rv; + + _put_path(filename); + r.x.bx = + 0x2002 | (flags & 0xfff0); /* r/w, no Int 24h, use caller-defined flags */ + r.x.dx = 0x0010; /* Create, fail if exists */ + r.x.si = __tb_offset; + if(use_lfn) + r.x.ax = 0x716c; + else + { + if (_osmajor == 0) + _get_dos_version (0); + if (_osmajor > 3) + r.x.ax = 0x6c00; + else + { + r.h.ah = 0x5b; + r.x.bx = 0; /* lose support for fancy flags in DOS 3.x */ + r.x.dx = __tb_offset; + r.x.si = 0; + } + } + r.x.cx = attrib & 0xffff; + r.x.ds = __tb_segment; + __dpmi_int(0x21, &r); + if(r.x.flags & 1) + { + errno = __doserr_to_errno(r.x.ax); + return -1; + } + __file_handle_set(r.x.ax, O_BINARY); + return r.x.ax; + } *** /dev/null Fri Mar 6 19:26:15 1998 --- src/libc/dos/io/_creat_n.txh Fri Mar 6 19:25:58 1998 *************** *** 0 **** --- 1,83 ---- + @node _creatnew, file system + @subheading Syntax + + @example + #include + #include + #include + + int _creatnew(const char *path, int attrib, int flags); + @end example + + @subheading Description + + This function creates a file given by @var{path} and opens it, like + @code{_creat} does, but only if it didn't already exist. If the named + file exists, @code{_creatnew} fails. (In contrast, @code{_creat} opens + existing files and overwrites their contents, see @ref{_creat}.) + + The attributes of the created file are determined by @var{attrib}. The + file is usually given the normal attribute (00H). If @var{attrib} is + non-zero, additional attributes will be set. The following macros, + defined on @code{}, can be used to control the attributes of the + created file (the associated numeric values appear in parentheses): + + @table @code + @item FA_RDONLY (1) + The file is created with the read-only bit set. + + @item FA_HIDDEN (2) + The file is created with the hidden bit set. Such files will not appear + in directory listings unless you use special options to the commands + which list files. + + @item FA_SYSTEM (4) + The file is created with the system bit set. Such files will not appear + in directory listings unless you use special options to the commands + which list files. + @end table + + Other bits (@code{FA_LABEL} and @code{FA_DIREC}) are ignored by DOS. + + The argument @var{flags} controls the sharing mode and the fine details + of how the file is handled by the operating system. The following + macros, defined on @code{}, can be used for this (associated + numeric values are given in parentheses): + + @table @code + @item SH_COMPAT (00h) + Opens the file in compatibility mode, which allows any other process to + open the file and read from the file any number of times. + + @item SH_DENYRW (10h) + Denies both read and write access by other processes. + + @item SH_DENYWR (20h) + Denies write access by other processes. + + @item SH_DENYRD (30h) + Denies read access by other processes. + + @item SH_DENYNO (40h) + Allows read and write access by other processes, but prevents other + processes from opening the file in compatibility mode. + @end table + + Note that the file is always open for both reading and writing; + @code{_creatnew} ignores any bits in the lower nibble of @var{flags} + (@code{O_RDONLY}, @code{O_WRONLY}, etc.). + + @code{_creatnew} calls DOS function 716Ch when long file names are + supported, 6C00h otherwise. (On DOS version 3.x, function 5B00h is + called which ignores the value of @var{flags}, since function 6C00h is + only supported by DOS 4.0 and later.) + + The file handle returned by @code{_creatnew} is set to binary mode. + + This function can be hooked by the Filesystem Extensions handlers, as + described in @ref{File System Extensions}. If you don't want this, you + should use @code{_dos_creatnew} (@pxref{_dos_creatnew}) instead. + + @subheading Return Value + + The new file descriptor, else -1 on error. *** src/libc/dos/io/makefile.~0 Fri Sep 20 02:38:56 1996 --- src/libc/dos/io/makefile Fri Mar 6 14:31:12 1998 *************** TOP=../.. *** 4,9 **** --- 4,10 ---- SRC += _chmod.c SRC += _close.c SRC += _creat.c + SRC += _creat_n.c SRC += _open.c SRC += _read.c SRC += _write.c *** include/io.h~0 Fri Sep 20 02:38:56 1996 --- include/io.h Fri Mar 6 15:46:40 1998 *************** *** 17,22 **** --- 17,23 ---- int chsize(int handle, long size); int _close(int _fd); int _creat(const char *_path, int _attrib); + int _creatnew(const char *_path, int _attrib, int _mode); ssize_t crlf2nl(char *_buffer, ssize_t _length); int _dos_lock(int _fd, long _offset, long _length); long filelength(int _handle); *** src/libc/ansi/stdio/tmpfile.c~0 Sun Aug 31 18:36:10 1997 --- src/libc/ansi/stdio/tmpfile.c Fri Mar 6 15:38:10 1998 *************** *** 1,32 **** /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #include #include #include #include #include #include FILE * tmpfile(void) { FILE *f; ! char *temp_name; ! char *n_t_r; ! temp_name = tmpnam(0); ! if (temp_name == 0) return 0; ! n_t_r = (char *)malloc(strlen(temp_name)+1); ! if (!n_t_r) return 0; ! f = fopen(temp_name, "wb+"); if (f) { ! f->_flag |= _IORMONCL; f->_name_to_remove = n_t_r; strcpy(f->_name_to_remove, temp_name); } return f; } --- 1,58 ---- + /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #include #include #include #include #include + #include + #include #include + #include FILE * tmpfile(void) { + int temp_fd; FILE *f; ! char *temp_name = tmpnam(0); ! char *n_t_r = (char *)malloc(L_tmpnam); ! if (!n_t_r) return 0; ! /* We could have a race condition, whereby another program ! (in another virtual machine, or if the temporary file is ! in a directory which is shared via a network) opens the ! file returned by `tmpnam' between the call above and the ! moment when we actually open the file below. This loop ! retries the call to `tmpnam' until we actually succeed ! to create the file which didn't exist before. */ ! do ! temp_fd = _creatnew(temp_name, 0, SH_DENYRW); ! while (temp_fd == -1 && (temp_name = tmpnam(0)) != 0); ! ! if (temp_name == 0) return 0; ! /* This should have been fdopen(temp_fd, "wb+"), but `fdopen' ! is non-ANSI. So we need to dump some of its guts here. Sigh... */ ! f = __alloc_file(); if (f) { ! f->_file = temp_fd; ! f->_cnt = 0; ! f->_bufsiz = 0; ! f->_flag = _IORMONCL | _IORW; f->_name_to_remove = n_t_r; strcpy(f->_name_to_remove, temp_name); + f->_base = f->_ptr = NULL; + } + else + { + close(temp_fd); + remove(temp_name); + free(n_t_r); } return f; } *** src/docs/kb/wc202.t~3 Sun Feb 1 00:24:04 1998 --- src/docs/kb/wc202.txi Fri Mar 6 19:31:56 1998 *************** *** 326,328 **** --- 326,342 ---- The new functions @code{basename} and @code{dirname} can be used to extract directory and basename parts from file names. + @findex basename + @findex dirname + + The new function @code{_creatnew} creates a file only if it didn't + already exist. + @findex _creatnew + + @code{tmpfile} makes sure it opens a file which is not and will not be + used by any other program. It does so by repeatedly calling + @code{tmpnam} until @code{_creatnew} succeeds to create a file which + didn't exist before. The temporary file is opened in DENY_ALL mode, so + that no other process can neither read from nor write to it. In case of + a failure, @code{tmpfile} does not leak memory anymore. + @findex tmpfile