*
* You should have received a copy of the GNU Lesser General Public
* License along with GLib; see the file COPYING.LIB. If not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "glibconfig.h"
#include <sys/stat.h>
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <stdlib.h>
+#ifdef G_OS_UNIX
+#include <unistd.h>
+#endif
#ifdef G_OS_WIN32
#include <windows.h>
#include <io.h>
#include <sys/vfs.h>
#endif
+
+/**
+ * SECTION:fileutils
+ * @title: File Utilities
+ * @short_description: various file-related functions
+ *
+ * There is a group of functions which wrap the common POSIX functions
+ * dealing with filenames (g_open(), g_rename(), g_mkdir(), g_stat(),
+ * g_unlink(), g_remove(), g_fopen(), g_freopen()). The point of these
+ * wrappers is to make it possible to handle file names with any Unicode
+ * characters in them on Windows without having to use ifdefs and the
+ * wide character API in the application code.
+ *
+ * The pathname argument should be in the GLib file name encoding.
+ * On POSIX this is the actual on-disk encoding which might correspond
+ * to the locale settings of the process (or the `G_FILENAME_ENCODING`
+ * environment variable), or not.
+ *
+ * On Windows the GLib file name encoding is UTF-8. Note that the
+ * Microsoft C library does not use UTF-8, but has separate APIs for
+ * current system code page and wide characters (UTF-16). The GLib
+ * wrappers call the wide character API if present (on modern Windows
+ * systems), otherwise convert to/from the system code page.
+ *
+ * Another group of functions allows to open and read directories
+ * in the GLib file name encoding. These are g_dir_open(),
+ * g_dir_read_name(), g_dir_rewind(), g_dir_close().
+ */
+
+/**
+ * GFileError:
+ * @G_FILE_ERROR_EXIST: Operation not permitted; only the owner of
+ * the file (or other resource) or processes with special privileges
+ * can perform the operation.
+ * @G_FILE_ERROR_ISDIR: File is a directory; you cannot open a directory
+ * for writing, or create or remove hard links to it.
+ * @G_FILE_ERROR_ACCES: Permission denied; the file permissions do not
+ * allow the attempted operation.
+ * @G_FILE_ERROR_NAMETOOLONG: Filename too long.
+ * @G_FILE_ERROR_NOENT: No such file or directory. This is a "file
+ * doesn't exist" error for ordinary files that are referenced in
+ * contexts where they are expected to already exist.
+ * @G_FILE_ERROR_NOTDIR: A file that isn't a directory was specified when
+ * a directory is required.
+ * @G_FILE_ERROR_NXIO: No such device or address. The system tried to
+ * use the device represented by a file you specified, and it
+ * couldn't find the device. This can mean that the device file was
+ * installed incorrectly, or that the physical device is missing or
+ * not correctly attached to the computer.
+ * @G_FILE_ERROR_NODEV: The underlying file system of the specified file
+ * does not support memory mapping.
+ * @G_FILE_ERROR_ROFS: The directory containing the new link can't be
+ * modified because it's on a read-only file system.
+ * @G_FILE_ERROR_TXTBSY: Text file busy.
+ * @G_FILE_ERROR_FAULT: You passed in a pointer to bad memory.
+ * (GLib won't reliably return this, don't pass in pointers to bad
+ * memory.)
+ * @G_FILE_ERROR_LOOP: Too many levels of symbolic links were encountered
+ * in looking up a file name. This often indicates a cycle of symbolic
+ * links.
+ * @G_FILE_ERROR_NOSPC: No space left on device; write operation on a
+ * file failed because the disk is full.
+ * @G_FILE_ERROR_NOMEM: No memory available. The system cannot allocate
+ * more virtual memory because its capacity is full.
+ * @G_FILE_ERROR_MFILE: The current process has too many files open and
+ * can't open any more. Duplicate descriptors do count toward this
+ * limit.
+ * @G_FILE_ERROR_NFILE: There are too many distinct file openings in the
+ * entire system.
+ * @G_FILE_ERROR_BADF: Bad file descriptor; for example, I/O on a
+ * descriptor that has been closed or reading from a descriptor open
+ * only for writing (or vice versa).
+ * @G_FILE_ERROR_INVAL: Invalid argument. This is used to indicate
+ * various kinds of problems with passing the wrong argument to a
+ * library function.
+ * @G_FILE_ERROR_PIPE: Broken pipe; there is no process reading from the
+ * other end of a pipe. Every library function that returns this
+ * error code also generates a 'SIGPIPE' signal; this signal
+ * terminates the program if not handled or blocked. Thus, your
+ * program will never actually see this code unless it has handled
+ * or blocked 'SIGPIPE'.
+ * @G_FILE_ERROR_AGAIN: Resource temporarily unavailable; the call might
+ * work if you try again later.
+ * @G_FILE_ERROR_INTR: Interrupted function call; an asynchronous signal
+ * occurred and prevented completion of the call. When this
+ * happens, you should try the call again.
+ * @G_FILE_ERROR_IO: Input/output error; usually used for physical read
+ * or write errors. i.e. the disk or other physical device hardware
+ * is returning errors.
+ * @G_FILE_ERROR_PERM: Operation not permitted; only the owner of the
+ * file (or other resource) or processes with special privileges can
+ * perform the operation.
+ * @G_FILE_ERROR_NOSYS: Function not implemented; this indicates that
+ * the system is missing some functionality.
+ * @G_FILE_ERROR_FAILED: Does not correspond to a UNIX error code; this
+ * is the standard "failed for unspecified reason" error code present
+ * in all #GError error code enumerations. Returned if no specific
+ * code applies.
+ *
+ * Values corresponding to @errno codes returned from file operations
+ * on UNIX. Unlike @errno codes, GFileError values are available on
+ * all systems, even Windows. The exact meaning of each code depends
+ * on what sort of file operation you were performing; the UNIX
+ * documentation gives more details. The following error code descriptions
+ * come from the GNU C Library manual, and are under the copyright
+ * of that manual.
+ *
+ * It's not very portable to make detailed assumptions about exactly
+ * which errors will be returned from a given operation. Some errors
+ * don't occur on some systems, etc., sometimes there are subtle
+ * differences in when a system will report a given error, etc.
+ */
+
+/**
+ * G_FILE_ERROR:
+ *
+ * Error domain for file operations. Errors in this domain will
+ * be from the #GFileError enumeration. See #GError for information
+ * on error domains.
+ */
+
+/**
+ * GFileTest:
+ * @G_FILE_TEST_IS_REGULAR: %TRUE if the file is a regular file
+ * (not a directory). Note that this test will also return %TRUE
+ * if the tested file is a symlink to a regular file.
+ * @G_FILE_TEST_IS_SYMLINK: %TRUE if the file is a symlink.
+ * @G_FILE_TEST_IS_DIR: %TRUE if the file is a directory.
+ * @G_FILE_TEST_IS_EXECUTABLE: %TRUE if the file is executable.
+ * @G_FILE_TEST_EXISTS: %TRUE if the file exists. It may or may not
+ * be a regular file.
+ *
+ * A test to perform on a file using g_file_test().
+ */
+
/**
* g_mkdir_with_parents:
* @pathname: a pathname in the GLib file name encoding
* @test: bitfield of #GFileTest flags
*
* Returns %TRUE if any of the tests in the bitfield @test are
- * %TRUE. For example, <literal>(G_FILE_TEST_EXISTS |
- * G_FILE_TEST_IS_DIR)</literal> will return %TRUE if the file exists;
- * the check whether it's a directory doesn't matter since the existence
- * test is %TRUE. With the current set of available tests, there's no point
- * passing in more than one test at a time.
+ * %TRUE. For example, `(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)`
+ * will return %TRUE if the file exists; the check whether it's a
+ * directory doesn't matter since the existence test is %TRUE. With
+ * the current set of available tests, there's no point passing in
+ * more than one test at a time.
*
* Apart from %G_FILE_TEST_IS_SYMLINK all tests follow symbolic links,
* so for a symbolic link to a regular file g_file_test() will return
* For example, you might think you could use %G_FILE_TEST_IS_SYMLINK
* to know whether it is safe to write to a file without being
* tricked into writing into a different location. It doesn't work!
- * |[
- * /* DON'T DO THIS */
+ * |[<!-- language="C" -->
+ * // DON'T DO THIS
* if (!g_file_test (filename, G_FILE_TEST_IS_SYMLINK))
* {
* fd = g_open (filename, O_WRONLY);
- * /* write to fd */
+ * // write to fd
* }
* ]|
*
* %G_FILE_TEST_IS_SYMLINK will always return %FALSE. Testing for
* %G_FILE_TEST_IS_EXECUTABLE will just check that the file exists and
* its name indicates that it is executable, checking for well-known
- * extensions and those listed in the %PATHEXT environment variable.
+ * extensions and those listed in the `PATHEXT` environment variable.
*
- * Return value: whether a test was %TRUE
+ * Returns: whether a test was %TRUE
**/
gboolean
g_file_test (const gchar *filename,
#endif
}
-GQuark
-g_file_error_quark (void)
-{
- return g_quark_from_static_string ("g-file-error-quark");
-}
+G_DEFINE_QUARK (g-file-error-quark, g_file_error)
/**
* g_file_error_from_errno:
* @err_no: an "errno" value
*
- * Gets a #GFileError constant based on the passed-in @errno.
- * For example, if you pass in %EEXIST this function returns
- * #G_FILE_ERROR_EXIST. Unlike @errno values, you can portably
+ * Gets a #GFileError constant based on the passed-in @err_no.
+ * For example, if you pass in `EEXIST` this function returns
+ * #G_FILE_ERROR_EXIST. Unlike `errno` values, you can portably
* assume that all #GFileError values will exist.
*
* Normally a #GFileError value goes into a #GError returned
* from a function that manipulates files. So you would use
* g_file_error_from_errno() when constructing a #GError.
*
- * Return value: #GFileError corresponding to the given @errno
+ * Returns: #GFileError corresponding to the given @errno
**/
GFileError
g_file_error_from_errno (gint err_no)
#ifdef EEXIST
case EEXIST:
return G_FILE_ERROR_EXIST;
- break;
#endif
#ifdef EISDIR
case EISDIR:
return G_FILE_ERROR_ISDIR;
- break;
#endif
#ifdef EACCES
case EACCES:
return G_FILE_ERROR_ACCES;
- break;
#endif
#ifdef ENAMETOOLONG
case ENAMETOOLONG:
return G_FILE_ERROR_NAMETOOLONG;
- break;
#endif
#ifdef ENOENT
case ENOENT:
return G_FILE_ERROR_NOENT;
- break;
#endif
#ifdef ENOTDIR
case ENOTDIR:
return G_FILE_ERROR_NOTDIR;
- break;
#endif
#ifdef ENXIO
case ENXIO:
return G_FILE_ERROR_NXIO;
- break;
#endif
#ifdef ENODEV
case ENODEV:
return G_FILE_ERROR_NODEV;
- break;
#endif
#ifdef EROFS
case EROFS:
return G_FILE_ERROR_ROFS;
- break;
#endif
#ifdef ETXTBSY
case ETXTBSY:
return G_FILE_ERROR_TXTBSY;
- break;
#endif
#ifdef EFAULT
case EFAULT:
return G_FILE_ERROR_FAULT;
- break;
#endif
#ifdef ELOOP
case ELOOP:
return G_FILE_ERROR_LOOP;
- break;
#endif
#ifdef ENOSPC
case ENOSPC:
return G_FILE_ERROR_NOSPC;
- break;
#endif
#ifdef ENOMEM
case ENOMEM:
return G_FILE_ERROR_NOMEM;
- break;
#endif
#ifdef EMFILE
case EMFILE:
return G_FILE_ERROR_MFILE;
- break;
#endif
#ifdef ENFILE
case ENFILE:
return G_FILE_ERROR_NFILE;
- break;
#endif
#ifdef EBADF
case EBADF:
return G_FILE_ERROR_BADF;
- break;
#endif
#ifdef EINVAL
case EINVAL:
return G_FILE_ERROR_INVAL;
- break;
#endif
#ifdef EPIPE
case EPIPE:
return G_FILE_ERROR_PIPE;
- break;
#endif
#ifdef EAGAIN
case EAGAIN:
return G_FILE_ERROR_AGAIN;
- break;
#endif
#ifdef EINTR
case EINTR:
return G_FILE_ERROR_INTR;
- break;
#endif
#ifdef EIO
case EIO:
return G_FILE_ERROR_IO;
- break;
#endif
#ifdef EPERM
case EPERM:
return G_FILE_ERROR_PERM;
- break;
#endif
#ifdef ENOSYS
case ENOSYS:
return G_FILE_ERROR_NOSYS;
- break;
#endif
default:
return G_FILE_ERROR_FAILED;
- break;
}
}
+static char *
+format_error_message (const gchar *filename,
+ const gchar *format_string,
+ int saved_errno) G_GNUC_FORMAT(2);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+
+static char *
+format_error_message (const gchar *filename,
+ const gchar *format_string,
+ int saved_errno)
+{
+ gchar *display_name;
+ gchar *msg;
+
+ display_name = g_filename_display_name (filename);
+ msg = g_strdup_printf (format_string, display_name, g_strerror (saved_errno));
+ g_free (display_name);
+
+ return msg;
+}
+
+#pragma GCC diagnostic pop
+
+/* format string must have two '%s':
+ *
+ * - the place for the filename
+ * - the place for the strerror
+ */
+static void
+set_file_error (GError **error,
+ const gchar *filename,
+ const gchar *format_string,
+ int saved_errno)
+{
+ char *msg = format_error_message (filename, format_string, saved_errno);
+
+ g_set_error_literal (error, G_FILE_ERROR, g_file_error_from_errno (saved_errno),
+ msg);
+ g_free (msg);
+}
+
static gboolean
-get_contents_stdio (const gchar *display_filename,
+get_contents_stdio (const gchar *filename,
FILE *f,
gchar **contents,
gsize *length,
GError **error)
{
gchar buf[4096];
- gsize bytes;
+ gsize bytes; /* always <= sizeof(buf) */
gchar *str = NULL;
gsize total_bytes = 0;
gsize total_allocated = 0;
gchar *tmp;
+ gchar *display_filename;
g_assert (f != NULL);
bytes = fread (buf, 1, sizeof (buf), f);
save_errno = errno;
- while ((total_bytes + bytes + 1) > total_allocated)
+ if (total_bytes > G_MAXSIZE - bytes)
+ goto file_too_large;
+
+ /* Possibility of overflow eliminated above. */
+ while (total_bytes + bytes >= total_allocated)
{
if (str)
- total_allocated *= 2;
+ {
+ if (total_allocated > G_MAXSIZE / 2)
+ goto file_too_large;
+ total_allocated *= 2;
+ }
else
- total_allocated = MIN (bytes + 1, sizeof (buf));
+ {
+ total_allocated = MIN (bytes + 1, sizeof (buf));
+ }
tmp = g_try_realloc (str, total_allocated);
if (tmp == NULL)
{
+ display_filename = g_filename_display_name (filename);
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_NOMEM,
- _("Could not allocate %lu bytes to read file \"%s\""),
+ g_dngettext (GETTEXT_PACKAGE, "Could not allocate %lu byte to read file \"%s\"", "Could not allocate %lu bytes to read file \"%s\"", (gulong)total_allocated),
(gulong) total_allocated,
display_filename);
+ g_free (display_filename);
goto error;
}
if (ferror (f))
{
+ display_filename = g_filename_display_name (filename);
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (save_errno),
_("Error reading file '%s': %s"),
display_filename,
g_strerror (save_errno));
+ g_free (display_filename);
goto error;
}
+ g_assert (str != NULL);
memcpy (str + total_bytes, buf, bytes);
- if (total_bytes + bytes < total_bytes)
- {
- g_set_error (error,
- G_FILE_ERROR,
- G_FILE_ERROR_FAILED,
- _("File \"%s\" is too large"),
- display_filename);
-
- goto error;
- }
-
total_bytes += bytes;
}
return TRUE;
+ file_too_large:
+ display_filename = g_filename_display_name (filename);
+ g_set_error (error,
+ G_FILE_ERROR,
+ G_FILE_ERROR_FAILED,
+ _("File \"%s\" is too large"),
+ display_filename);
+ g_free (display_filename);
+
error:
g_free (str);
#ifndef G_OS_WIN32
static gboolean
-get_contents_regfile (const gchar *display_filename,
+get_contents_regfile (const gchar *filename,
struct stat *stat_buf,
gint fd,
gchar **contents,
gsize bytes_read;
gsize size;
gsize alloc_size;
+ gchar *display_filename;
size = stat_buf->st_size;
if (buf == NULL)
{
+ display_filename = g_filename_display_name (filename);
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_NOMEM,
- _("Could not allocate %lu bytes to read file \"%s\""),
+ g_dngettext (GETTEXT_PACKAGE, "Could not allocate %lu byte to read file \"%s\"", "Could not allocate %lu bytes to read file \"%s\"", (gulong)alloc_size),
(gulong) alloc_size,
display_filename);
-
+ g_free (display_filename);
goto error;
}
int save_errno = errno;
g_free (buf);
+ display_filename = g_filename_display_name (filename);
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (save_errno),
_("Failed to read from file '%s': %s"),
display_filename,
g_strerror (save_errno));
-
+ g_free (display_filename);
goto error;
}
}
{
struct stat stat_buf;
gint fd;
- gchar *display_filename = g_filename_display_name (filename);
/* O_BINARY useful on Cygwin */
fd = open (filename, O_RDONLY|O_BINARY);
if (fd < 0)
{
- int save_errno = errno;
-
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to open file '%s': %s"),
- display_filename,
- g_strerror (save_errno));
- g_free (display_filename);
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Failed to open file '%s': %s"),
+ saved_errno);
return FALSE;
}
/* I don't think this will ever fail, aside from ENOMEM, but. */
if (fstat (fd, &stat_buf) < 0)
{
- int save_errno = errno;
-
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Failed to get attributes of file '%s': fstat() failed: %s"),
+ saved_errno);
close (fd);
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to get attributes of file '%s': fstat() failed: %s"),
- display_filename,
- g_strerror (save_errno));
- g_free (display_filename);
return FALSE;
}
if (stat_buf.st_size > 0 && S_ISREG (stat_buf.st_mode))
{
- gboolean retval = get_contents_regfile (display_filename,
+ gboolean retval = get_contents_regfile (filename,
&stat_buf,
fd,
contents,
length,
error);
- g_free (display_filename);
return retval;
}
if (f == NULL)
{
- int save_errno = errno;
-
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to open file '%s': fdopen() failed: %s"),
- display_filename,
- g_strerror (save_errno));
- g_free (display_filename);
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Failed to open file '%s': fdopen() failed: %s"),
+ saved_errno);
return FALSE;
}
- retval = get_contents_stdio (display_filename, f, contents, length, error);
- g_free (display_filename);
+ retval = get_contents_stdio (filename, f, contents, length, error);
return retval;
}
{
FILE *f;
gboolean retval;
- gchar *display_filename = g_filename_display_name (filename);
- int save_errno;
f = g_fopen (filename, "rb");
- save_errno = errno;
if (f == NULL)
{
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to open file '%s': %s"),
- display_filename,
- g_strerror (save_errno));
- g_free (display_filename);
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Failed to open file '%s': %s"),
+ saved_errno);
return FALSE;
}
- retval = get_contents_stdio (display_filename, f, contents, length, error);
- g_free (display_filename);
+ retval = get_contents_stdio (filename, f, contents, length, error);
return retval;
}
* codes are those in the #GFileError enumeration. In the error case,
* @contents is set to %NULL and @length is set to zero.
*
- * Return value: %TRUE on success, %FALSE if an error occurred
+ * Returns: %TRUE on success, %FALSE if an error occurred
**/
gboolean
g_file_get_contents (const gchar *filename,
GError **err)
{
gchar *tmp_name;
- gchar *display_name;
gchar *retval;
- FILE *file;
gint fd;
- int save_errno;
retval = NULL;
-
+
tmp_name = g_strdup_printf ("%s.XXXXXX", dest_file);
errno = 0;
fd = g_mkstemp_full (tmp_name, O_RDWR | O_BINARY, 0666);
- save_errno = errno;
- display_name = g_filename_display_name (tmp_name);
-
if (fd == -1)
{
- g_set_error (err,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to create file '%s': %s"),
- display_name, g_strerror (save_errno));
-
+ int saved_errno = errno;
+ set_file_error (err,
+ tmp_name, _("Failed to create file '%s': %s"),
+ saved_errno);
goto out;
}
- errno = 0;
- file = fdopen (fd, "wb");
- if (!file)
+#ifdef HAVE_FALLOCATE
+ if (length > 0)
{
- save_errno = errno;
- g_set_error (err,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to open file '%s' for writing: fdopen() failed: %s"),
- display_name,
- g_strerror (save_errno));
-
- close (fd);
- g_unlink (tmp_name);
-
- goto out;
+ /* We do this on a 'best effort' basis... It may not be supported
+ * on the underlying filesystem.
+ */
+ (void) fallocate (fd, 0, 0, length);
}
-
- if (length > 0)
+#endif
+ while (length > 0)
{
- gsize n_written;
-
- errno = 0;
+ gssize s;
- n_written = fwrite (contents, 1, length, file);
+ s = write (fd, contents, length);
- if (n_written < length)
- {
- save_errno = errno;
-
- g_set_error (err,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to write file '%s': fwrite() failed: %s"),
- display_name,
- g_strerror (save_errno));
+ if (s < 0)
+ {
+ int saved_errno = errno;
+ if (saved_errno == EINTR)
+ continue;
- fclose (file);
- g_unlink (tmp_name);
-
- goto out;
- }
- }
+ set_file_error (err,
+ tmp_name, _("Failed to write file '%s': write() failed: %s"),
+ saved_errno);
+ close (fd);
+ g_unlink (tmp_name);
- errno = 0;
- if (fflush (file) != 0)
- {
- save_errno = errno;
-
- g_set_error (err,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to write file '%s': fflush() failed: %s"),
- display_name,
- g_strerror (save_errno));
+ goto out;
+ }
- fclose (file);
- g_unlink (tmp_name);
-
- goto out;
+ g_assert (s <= length);
+
+ contents += s;
+ length -= s;
}
#ifdef BTRFS_SUPER_MAGIC
goto no_fsync;
}
#endif
-
+
#ifdef HAVE_FSYNC
{
struct stat statbuf;
* the new and the old file on some filesystems. (I.E. those that don't
* guarantee the data is written to the disk before the metadata.)
*/
- if (g_lstat (dest_file, &statbuf) == 0 &&
- statbuf.st_size > 0 &&
- fsync (fileno (file)) != 0)
+ if (g_lstat (dest_file, &statbuf) == 0 && statbuf.st_size > 0 && fsync (fd) != 0)
{
- save_errno = errno;
-
- g_set_error (err,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to write file '%s': fsync() failed: %s"),
- display_name,
- g_strerror (save_errno));
-
- fclose (file);
- g_unlink (tmp_name);
-
- goto out;
+ int saved_errno = errno;
+ set_file_error (err,
+ tmp_name, _("Failed to write file '%s': fsync() failed: %s"),
+ saved_errno);
+ close (fd);
+ g_unlink (tmp_name);
+
+ goto out;
}
}
#endif
+
+#ifdef BTRFS_SUPER_MAGIC
no_fsync:
-
- errno = 0;
- if (fclose (file) == EOF)
- {
- save_errno = errno;
-
- g_set_error (err,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to close file '%s': fclose() failed: %s"),
- display_name,
- g_strerror (save_errno));
+#endif
- fclose (file);
+ errno = 0;
+ if (!g_close (fd, err))
+ {
g_unlink (tmp_name);
-
+
goto out;
}
retval = g_strdup (tmp_name);
-
+
out:
g_free (tmp_name);
- g_free (display_name);
-
+
return retval;
}
*
* This write is atomic in the sense that it is first written to a temporary
* file which is then renamed to the final name. Notes:
- * <itemizedlist>
- * <listitem>
- * On Unix, if @filename already exists hard links to @filename will break.
- * Also since the file is recreated, existing permissions, access control
- * lists, metadata etc. may be lost. If @filename is a symbolic link,
- * the link itself will be replaced, not the linked file.
- * </listitem>
- * <listitem>
- * On Windows renaming a file will not remove an existing file with the
+ *
+ * - On UNIX, if @filename already exists hard links to @filename will break.
+ * Also since the file is recreated, existing permissions, access control
+ * lists, metadata etc. may be lost. If @filename is a symbolic link,
+ * the link itself will be replaced, not the linked file.
+ *
+ * - On Windows renaming a file will not remove an existing file with the
* new name, so on Windows there is a race condition between the existing
* file being removed and the temporary file being renamed.
- * </listitem>
- * <listitem>
- * On Windows there is no way to remove a file that is open to some
+ *
+ * - On Windows there is no way to remove a file that is open to some
* process, or mapped into memory. Thus, this function will fail if
* @filename already exists and is open.
- * </listitem>
- * </itemizedlist>
*
- * If the call was sucessful, it returns %TRUE. If the call was not successful,
+ * If the call was successful, it returns %TRUE. If the call was not successful,
* it returns %FALSE and sets @error. The error domain is #G_FILE_ERROR.
* Possible error codes are those in the #GFileError enumeration.
*
* Note that the name for the temporary file is constructed by appending up
* to 7 characters to @filename.
*
- * Return value: %TRUE on success, %FALSE if an error occurred
+ * Returns: %TRUE on success, %FALSE if an error occurred
*
* Since: 2.8
- **/
+ */
gboolean
g_file_set_contents (const gchar *filename,
const gchar *contents,
if (g_unlink (filename) == -1)
{
- gchar *display_filename = g_filename_display_name (filename);
-
- int save_errno = errno;
-
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Existing file '%s' could not be removed: g_unlink() failed: %s"),
- display_filename,
- g_strerror (save_errno));
-
- g_free (display_filename);
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Existing file '%s' could not be removed: g_unlink() failed: %s"),
+ saved_errno);
g_unlink (tmp_filename);
retval = FALSE;
goto out;
return retval;
}
-/**
- * g_mkstemp_full:
- * @tmpl: template filename
- * @flags: flags to pass to an open() call in addition to O_EXCL and
- * O_CREAT, which are passed automatically
- * @mode: permissios to create the temporary file with
- *
- * Opens a temporary file. See the mkstemp() documentation
- * on most UNIX-like systems.
- *
- * The parameter is a string that should follow the rules for
- * mkstemp() templates, i.e. contain the string "XXXXXX".
- * g_mkstemp_full() is slightly more flexible than mkstemp()
- * in that the sequence does not have to occur at the very end of the
- * template and you can pass a @mode and additional @flags. The X
- * string will be modified to form the name of a file that didn't exist.
- * The string should be in the GLib file name encoding. Most importantly,
- * on Windows it should be in UTF-8.
- *
- * Return value: A file handle (as from open()) to the file
- * opened for reading and writing. The file handle should be
- * closed with close(). In case of errors, -1 is returned.
- *
- * Since: 2.22
- */
/*
- * g_mkstemp_full based on the mkstemp implementation from the GNU C library.
+ * get_tmp_file based on the mkstemp implementation from the GNU C library.
* Copyright (C) 1991,92,93,94,95,96,97,98,99 Free Software Foundation, Inc.
*/
-gint
-g_mkstemp_full (gchar *tmpl,
- int flags,
- int mode)
+typedef gint (*GTmpFileCallback) (const gchar *, gint, gint);
+
+static gint
+get_tmp_file (gchar *tmpl,
+ GTmpFileCallback f,
+ int flags,
+ int mode)
{
char *XXXXXX;
int count, fd;
g_return_val_if_fail (tmpl != NULL, -1);
-
/* find the last occurrence of "XXXXXX" */
XXXXXX = g_strrstr (tmpl, "XXXXXX");
v /= NLETTERS;
XXXXXX[5] = letters[v % NLETTERS];
- /* tmpl is in UTF-8 on Windows, thus use g_open() */
- fd = g_open (tmpl, flags | O_CREAT | O_EXCL, mode);
+ fd = f (tmpl, flags, mode);
if (fd >= 0)
- return fd;
+ return fd;
else if (errno != EEXIST)
- /* Any other error will apply also to other names we might
- * try, and there are 2^32 or so of them, so give up now.
- */
- return -1;
+ /* Any other error will apply also to other names we might
+ * try, and there are 2^32 or so of them, so give up now.
+ */
+ return -1;
}
/* We got out of the loop because we ran out of combinations to try. */
return -1;
}
+/* Some GTmpFileCallback implementations.
+ *
+ * Note: we cannot use open() or g_open() directly because even though
+ * they appear compatible, they may be vararg functions and calling
+ * varargs functions through a non-varargs type is undefined.
+ */
+static gint
+wrap_g_mkdir (const gchar *filename,
+ int flags G_GNUC_UNUSED,
+ int mode)
+{
+ /* tmpl is in UTF-8 on Windows, thus use g_mkdir() */
+ return g_mkdir (filename, mode);
+}
+
+static gint
+wrap_g_open (const gchar *filename,
+ int flags,
+ int mode)
+{
+ return g_open (filename, flags, mode);
+}
+
/**
- * g_mkstemp:
- * @tmpl: template filename
+ * g_mkdtemp_full:
+ * @tmpl: (type filename): template directory name
+ * @mode: permissions to create the temporary directory with
*
- * Opens a temporary file. See the mkstemp() documentation
- * on most UNIX-like systems.
+ * Creates a temporary directory. See the mkdtemp() documentation
+ * on most UNIX-like systems.
*
* The parameter is a string that should follow the rules for
- * mkstemp() templates, i.e. contain the string "XXXXXX".
- * g_mkstemp() is slightly more flexible than mkstemp()
- * in that the sequence does not have to occur at the very end of the
- * template. The X string will
- * be modified to form the name of a file that didn't exist.
- * The string should be in the GLib file name encoding. Most importantly,
- * on Windows it should be in UTF-8.
+ * mkdtemp() templates, i.e. contain the string "XXXXXX".
+ * g_mkdtemp() is slightly more flexible than mkdtemp() in that the
+ * sequence does not have to occur at the very end of the template
+ * and you can pass a @mode. The X string will be modified to form
+ * the name of a directory that didn't exist. The string should be
+ * in the GLib file name encoding. Most importantly, on Windows it
+ * should be in UTF-8.
*
- * Return value: A file handle (as from open()) to the file
- * opened for reading and writing. The file is opened in binary mode
- * on platforms where there is a difference. The file handle should be
- * closed with close(). In case of errors, -1 is returned.
- */
-gint
-g_mkstemp (gchar *tmpl)
+ * Returns: A pointer to @tmpl, which has been modified
+ * to hold the directory name. In case of errors, %NULL is
+ * returned, and %errno will be set.
+ *
+ * Since: 2.30
+ */
+gchar *
+g_mkdtemp_full (gchar *tmpl,
+ gint mode)
{
- return g_mkstemp_full (tmpl, O_RDWR | O_BINARY, 0600);
+ if (get_tmp_file (tmpl, wrap_g_mkdir, 0, mode) == -1)
+ return NULL;
+ else
+ return tmpl;
}
/**
- * g_file_open_tmp:
- * @tmpl: Template for file name, as in g_mkstemp(), basename only,
- * or %NULL, to a default template
- * @name_used: location to store actual name used, or %NULL
- * @error: return location for a #GError
- *
- * Opens a file for writing in the preferred directory for temporary
- * files (as returned by g_get_tmp_dir()).
+ * g_mkdtemp:
+ * @tmpl: (type filename): template directory name
*
- * @tmpl should be a string in the GLib file name encoding containing
- * a sequence of six 'X' characters, as the parameter to g_mkstemp().
- * However, unlike these functions, the template should only be a
- * basename, no directory components are allowed. If template is
- * %NULL, a default template is used.
+ * Creates a temporary directory. See the mkdtemp() documentation
+ * on most UNIX-like systems.
*
- * Note that in contrast to g_mkstemp() (and mkstemp())
- * @tmpl is not modified, and might thus be a read-only literal string.
+ * The parameter is a string that should follow the rules for
+ * mkdtemp() templates, i.e. contain the string "XXXXXX".
+ * g_mkdtemp() is slightly more flexible than mkdtemp() in that the
+ * sequence does not have to occur at the very end of the template
+ * and you can pass a @mode and additional @flags. The X string will
+ * be modified to form the name of a directory that didn't exist.
+ * The string should be in the GLib file name encoding. Most importantly,
+ * on Windows it should be in UTF-8.
*
- * The actual name used is returned in @name_used if non-%NULL. This
- * string should be freed with g_free() when not needed any longer.
- * The returned name is in the GLib file name encoding.
+ * Returns: A pointer to @tmpl, which has been modified
+ * to hold the directory name. In case of errors, %NULL is
+ * returned and %errno will be set.
*
- * Return value: A file handle (as from open()) to
- * the file opened for reading and writing. The file is opened in binary
- * mode on platforms where there is a difference. The file handle should be
- * closed with close(). In case of errors, -1 is returned
- * and @error will be set.
- **/
-gint
-g_file_open_tmp (const gchar *tmpl,
- gchar **name_used,
- GError **error)
+ * Since: 2.30
+ */
+gchar *
+g_mkdtemp (gchar *tmpl)
{
- int retval;
- const char *tmpdir;
- const char *sep;
+ return g_mkdtemp_full (tmpl, 0700);
+}
+
+/**
+ * g_mkstemp_full:
+ * @tmpl: (type filename): template filename
+ * @flags: flags to pass to an open() call in addition to O_EXCL
+ * and O_CREAT, which are passed automatically
+ * @mode: permissions to create the temporary file with
+ *
+ * Opens a temporary file. See the mkstemp() documentation
+ * on most UNIX-like systems.
+ *
+ * The parameter is a string that should follow the rules for
+ * mkstemp() templates, i.e. contain the string "XXXXXX".
+ * g_mkstemp_full() is slightly more flexible than mkstemp()
+ * in that the sequence does not have to occur at the very end of the
+ * template and you can pass a @mode and additional @flags. The X
+ * string will be modified to form the name of a file that didn't exist.
+ * The string should be in the GLib file name encoding. Most importantly,
+ * on Windows it should be in UTF-8.
+ *
+ * Returns: A file handle (as from open()) to the file
+ * opened for reading and writing. The file handle should be
+ * closed with close(). In case of errors, -1 is returned
+ * and %errno will be set.
+ *
+ * Since: 2.22
+ */
+gint
+g_mkstemp_full (gchar *tmpl,
+ gint flags,
+ gint mode)
+{
+ /* tmpl is in UTF-8 on Windows, thus use g_open() */
+ return get_tmp_file (tmpl, wrap_g_open,
+ flags | O_CREAT | O_EXCL, mode);
+}
+
+/**
+ * g_mkstemp:
+ * @tmpl: (type filename): template filename
+ *
+ * Opens a temporary file. See the mkstemp() documentation
+ * on most UNIX-like systems.
+ *
+ * The parameter is a string that should follow the rules for
+ * mkstemp() templates, i.e. contain the string "XXXXXX".
+ * g_mkstemp() is slightly more flexible than mkstemp() in that the
+ * sequence does not have to occur at the very end of the template.
+ * The X string will be modified to form the name of a file that
+ * didn't exist. The string should be in the GLib file name encoding.
+ * Most importantly, on Windows it should be in UTF-8.
+ *
+ * Returns: A file handle (as from open()) to the file
+ * opened for reading and writing. The file is opened in binary
+ * mode on platforms where there is a difference. The file handle
+ * should be closed with close(). In case of errors, -1 is
+ * returned and %errno will be set.
+ */
+gint
+g_mkstemp (gchar *tmpl)
+{
+ return g_mkstemp_full (tmpl, O_RDWR | O_BINARY, 0600);
+}
+
+static gint
+g_get_tmp_name (const gchar *tmpl,
+ gchar **name_used,
+ GTmpFileCallback f,
+ gint flags,
+ gint mode,
+ GError **error)
+{
+ int retval;
+ const char *tmpdir;
+ const char *sep;
char *fulltemplate;
const char *slash;
c[1] = '\0';
g_set_error (error,
- G_FILE_ERROR,
- G_FILE_ERROR_FAILED,
- _("Template '%s' invalid, should not contain a '%s'"),
- display_tmpl, c);
+ G_FILE_ERROR,
+ G_FILE_ERROR_FAILED,
+ _("Template '%s' invalid, should not contain a '%s'"),
+ display_tmpl, c);
g_free (display_tmpl);
return -1;
}
-
+
if (strstr (tmpl, "XXXXXX") == NULL)
{
gchar *display_tmpl = g_filename_display_name (tmpl);
g_set_error (error,
- G_FILE_ERROR,
- G_FILE_ERROR_FAILED,
- _("Template '%s' doesn't contain XXXXXX"),
- display_tmpl);
+ G_FILE_ERROR,
+ G_FILE_ERROR_FAILED,
+ _("Template '%s' doesn't contain XXXXXX"),
+ display_tmpl);
g_free (display_tmpl);
return -1;
}
fulltemplate = g_strconcat (tmpdir, sep, tmpl, NULL);
- retval = g_mkstemp (fulltemplate);
-
+ retval = get_tmp_file (fulltemplate, f, flags, mode);
if (retval == -1)
{
- int save_errno = errno;
- gchar *display_fulltemplate = g_filename_display_name (fulltemplate);
-
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to create file '%s': %s"),
- display_fulltemplate, g_strerror (save_errno));
- g_free (display_fulltemplate);
+ int saved_errno = errno;
+ set_file_error (error,
+ fulltemplate,
+ _("Failed to create file '%s': %s"),
+ saved_errno);
g_free (fulltemplate);
return -1;
}
- if (name_used)
- *name_used = fulltemplate;
- else
- g_free (fulltemplate);
+ *name_used = fulltemplate;
return retval;
}
+/**
+ * g_file_open_tmp:
+ * @tmpl: (type filename) (allow-none): Template for file name, as in
+ * g_mkstemp(), basename only, or %NULL for a default template
+ * @name_used: (out) (type filename): location to store actual name used,
+ * or %NULL
+ * @error: return location for a #GError
+ *
+ * Opens a file for writing in the preferred directory for temporary
+ * files (as returned by g_get_tmp_dir()).
+ *
+ * @tmpl should be a string in the GLib file name encoding containing
+ * a sequence of six 'X' characters, as the parameter to g_mkstemp().
+ * However, unlike these functions, the template should only be a
+ * basename, no directory components are allowed. If template is
+ * %NULL, a default template is used.
+ *
+ * Note that in contrast to g_mkstemp() (and mkstemp()) @tmpl is not
+ * modified, and might thus be a read-only literal string.
+ *
+ * Upon success, and if @name_used is non-%NULL, the actual name used
+ * is returned in @name_used. This string should be freed with g_free()
+ * when not needed any longer. The returned name is in the GLib file
+ * name encoding.
+ *
+ * Returns: A file handle (as from open()) to the file opened for
+ * reading and writing. The file is opened in binary mode on platforms
+ * where there is a difference. The file handle should be closed with
+ * close(). In case of errors, -1 is returned and @error will be set.
+ */
+gint
+g_file_open_tmp (const gchar *tmpl,
+ gchar **name_used,
+ GError **error)
+{
+ gchar *fulltemplate;
+ gint result;
+
+ result = g_get_tmp_name (tmpl, &fulltemplate,
+ wrap_g_open,
+ O_CREAT | O_EXCL | O_RDWR | O_BINARY,
+ 0600,
+ error);
+ if (result != -1)
+ {
+ if (name_used)
+ *name_used = fulltemplate;
+ else
+ g_free (fulltemplate);
+ }
+
+ return result;
+}
+
+/**
+ * g_dir_make_tmp:
+ * @tmpl: (type filename) (allow-none): Template for directory name,
+ * as in g_mkdtemp(), basename only, or %NULL for a default template
+ * @error: return location for a #GError
+ *
+ * Creates a subdirectory in the preferred directory for temporary
+ * files (as returned by g_get_tmp_dir()).
+ *
+ * @tmpl should be a string in the GLib file name encoding containing
+ * a sequence of six 'X' characters, as the parameter to g_mkstemp().
+ * However, unlike these functions, the template should only be a
+ * basename, no directory components are allowed. If template is
+ * %NULL, a default template is used.
+ *
+ * Note that in contrast to g_mkdtemp() (and mkdtemp()) @tmpl is not
+ * modified, and might thus be a read-only literal string.
+ *
+ * Returns: (type filename): The actual name used. This string
+ * should be freed with g_free() when not needed any longer and is
+ * is in the GLib file name encoding. In case of errors, %NULL is
+ * returned and @error will be set.
+ *
+ * Since: 2.30
+ */
+gchar *
+g_dir_make_tmp (const gchar *tmpl,
+ GError **error)
+{
+ gchar *fulltemplate;
+
+ if (g_get_tmp_name (tmpl, &fulltemplate, wrap_g_mkdir, 0, 0700, error) == -1)
+ return NULL;
+ else
+ return fulltemplate;
+}
+
static gchar *
g_build_path_va (const gchar *separator,
const gchar *first_element,
* as a string array, instead of varargs. This function is mainly
* meant for language bindings.
*
- * Return value: a newly-allocated string that must be freed with g_free().
+ * Returns: a newly-allocated string that must be freed with g_free().
*
* Since: 2.8
*/
* the same as the number of trailing copies of the separator on
* the last non-empty element. (Determination of the number of
* trailing copies is done without stripping leading copies, so
- * if the separator is <literal>ABA</literal>, <literal>ABABA</literal>
- * has 1 trailing copy.)
+ * if the separator is `ABA`, then `ABABA` has 1 trailing copy.)
*
* However, if there is only a single non-empty element, and there
* are no characters in that element not part of the leading or
* copies of the separator, elements consisting only of copies
* of the separator are ignored.
*
- * Return value: a newly-allocated string that must be freed with g_free().
+ * Returns: a newly-allocated string that must be freed with g_free().
**/
gchar *
g_build_path (const gchar *separator,
* as a string array, instead of varargs. This function is mainly
* meant for language bindings.
*
- * Return value: a newly-allocated string that must be freed with g_free().
+ * Returns: a newly-allocated string that must be freed with g_free().
*
* Since: 2.8
*/
* Creates a filename from a series of elements using the correct
* separator for filenames.
*
- * On Unix, this function behaves identically to <literal>g_build_path
- * (G_DIR_SEPARATOR_S, first_element, ....)</literal>.
+ * On Unix, this function behaves identically to `g_build_path
+ * (G_DIR_SEPARATOR_S, first_element, ....)`.
*
* On Windows, it takes into account that either the backslash
- * (<literal>\</literal> or slash (<literal>/</literal>) can be used
- * as separator in filenames, but otherwise behaves as on Unix. When
- * file pathname separators need to be inserted, the one that last
- * previously occurred in the parameters (reading from left to right)
- * is used.
+ * (`\` or slash (`/`) can be used as separator in filenames, but
+ * otherwise behaves as on UNIX. When file pathname separators need
+ * to be inserted, the one that last previously occurred in the
+ * parameters (reading from left to right) is used.
*
* No attempt is made to force the resulting filename to be an absolute
* path. If the first element is a relative path, the result will
* be a relative path.
*
- * Return value: a newly-allocated string that must be freed with g_free().
+ * Returns: a newly-allocated string that must be freed with g_free().
**/
gchar *
g_build_filename (const gchar *first_element,
return str;
}
-#define KILOBYTE_FACTOR (G_GOFFSET_CONSTANT (1000))
-#define MEGABYTE_FACTOR (KILOBYTE_FACTOR * KILOBYTE_FACTOR)
-#define GIGABYTE_FACTOR (MEGABYTE_FACTOR * KILOBYTE_FACTOR)
-#define TERABYTE_FACTOR (GIGABYTE_FACTOR * KILOBYTE_FACTOR)
-#define PETABYTE_FACTOR (TERABYTE_FACTOR * KILOBYTE_FACTOR)
-#define EXABYTE_FACTOR (PETABYTE_FACTOR * KILOBYTE_FACTOR)
-
-#define KIBIBYTE_FACTOR (G_GOFFSET_CONSTANT (1024))
-#define MEBIBYTE_FACTOR (KIBIBYTE_FACTOR * KIBIBYTE_FACTOR)
-#define GIBIBYTE_FACTOR (MEBIBYTE_FACTOR * KIBIBYTE_FACTOR)
-#define TEBIBYTE_FACTOR (GIBIBYTE_FACTOR * KIBIBYTE_FACTOR)
-#define PEBIBYTE_FACTOR (TEBIBYTE_FACTOR * KIBIBYTE_FACTOR)
-#define EXBIBYTE_FACTOR (PEBIBYTE_FACTOR * KIBIBYTE_FACTOR)
-
/**
- * g_format_size:
- * @size: a size in bytes
- *
- * Formats a size (for example the size of a file) into a human readable
- * string. Sizes are rounded to the nearest size prefix (kB, MB, GB)
- * and are displayed rounded to the nearest tenth. E.g. the file size
- * 3292528 bytes will be converted into the string "3.2 MB".
- *
- * The prefix units base is 1000 (i.e. 1 kB is 1000 bytes).
- *
- * This string should be freed with g_free() when not needed any longer.
+ * g_file_read_link:
+ * @filename: the symbolic link
+ * @error: return location for a #GError
*
- * See g_format_size_full() for more options about how the size might be
- * formatted.
+ * Reads the contents of the symbolic link @filename like the POSIX
+ * readlink() function. The returned string is in the encoding used
+ * for filenames. Use g_filename_to_utf8() to convert it to UTF-8.
*
- * Returns: a newly-allocated formatted string containing a human readable
- * file size.
+ * Returns: A newly-allocated string with the contents of the symbolic link,
+ * or %NULL if an error occurred.
*
- * Since: 2.30
- **/
+ * Since: 2.4
+ */
gchar *
-g_format_size (guint64 size)
+g_file_read_link (const gchar *filename,
+ GError **error)
{
- return g_format_size_full (size, G_FORMAT_SIZE_DEFAULT);
+#ifdef HAVE_READLINK
+ gchar *buffer;
+ guint size;
+ gint read_size;
+
+ size = 256;
+ buffer = g_malloc (size);
+
+ while (TRUE)
+ {
+ read_size = readlink (filename, buffer, size);
+ if (read_size < 0)
+ {
+ int saved_errno = errno;
+ set_file_error (error,
+ filename,
+ _("Failed to read the symbolic link '%s': %s"),
+ saved_errno);
+ g_free (buffer);
+ return NULL;
+ }
+
+ if (read_size < size)
+ {
+ buffer[read_size] = 0;
+ return buffer;
+ }
+
+ size *= 2;
+ buffer = g_realloc (buffer, size);
+ }
+#else
+ g_set_error_literal (error,
+ G_FILE_ERROR,
+ G_FILE_ERROR_INVAL,
+ _("Symbolic links not supported"));
+
+ return NULL;
+#endif
}
/**
- * g_format_size_full:
- * @size: a size in bytes
- * @flags: #GFormatSizeFlags to modify the output
+ * g_path_is_absolute:
+ * @file_name: a file name
*
- * Formats a size.
+ * Returns %TRUE if the given @file_name is an absolute file name.
+ * Note that this is a somewhat vague concept on Windows.
*
- * This function is similar to g_format_size() but allows for flags that
- * modify the output. See #GFormatSizeFlags.
+ * On POSIX systems, an absolute file name is well-defined. It always
+ * starts from the single root directory. For example "/usr/local".
*
- * Returns: a newly-allocated formatted string containing a human
- * readable file size.
+ * On Windows, the concepts of current drive and drive-specific
+ * current directory introduce vagueness. This function interprets as
+ * an absolute file name one that either begins with a directory
+ * separator such as "\Users\tml" or begins with the root on a drive,
+ * for example "C:\Windows". The first case also includes UNC paths
+ * such as "\\myserver\docs\foo". In all cases, either slashes or
+ * backslashes are accepted.
*
- * Since: 2.30
- **/
-/**
- * GFormatSizeFlags:
- * @G_FORMAT_SIZE_DEFAULT: behave the same as g_format_size()
- * @G_FORMAT_SIZE_LONG_FORMAT: include the exact number of bytes as part
- * of the returned string. For example,
- * "45.6 kB (45,612 bytes)".
- * @G_FORMAT_SIZE_IEC_UNITS: use IEC (base 1024) units with "KiB"-style
- * suffixes. IEC units should only be used
- * for reporting things with a strong "power
- * of 2" basis, like RAM sizes or RAID stripe
- * sizes. Network and storage sizes should
- * be reported in the normal SI units.
- *
- * Flags to modify the format of the string returned by
- * g_format_size_full().
- **/
-gchar *
-g_format_size_full (guint64 size,
- GFormatSizeFlags flags)
+ * Note that a file name relative to the current drive root does not
+ * truly specify a file uniquely over time and across processes, as
+ * the current drive is a per-process value and can be changed.
+ *
+ * File names relative the current directory on some specific drive,
+ * such as "D:foo/bar", are not interpreted as absolute by this
+ * function, but they obviously are not relative to the normal current
+ * directory as returned by getcwd() or g_get_current_dir()
+ * either. Such paths should be avoided, or need to be handled using
+ * Windows-specific code.
+ *
+ * Returns: %TRUE if @file_name is absolute
+ */
+gboolean
+g_path_is_absolute (const gchar *file_name)
{
- GString *string;
+ g_return_val_if_fail (file_name != NULL, FALSE);
- string = g_string_new (NULL);
+ if (G_IS_DIR_SEPARATOR (file_name[0]))
+ return TRUE;
- if (flags & G_FORMAT_SIZE_IEC_UNITS)
+#ifdef G_OS_WIN32
+ /* Recognize drive letter on native Windows */
+ if (g_ascii_isalpha (file_name[0]) &&
+ file_name[1] == ':' && G_IS_DIR_SEPARATOR (file_name[2]))
+ return TRUE;
+#endif
+
+ return FALSE;
+}
+
+/**
+ * g_path_skip_root:
+ * @file_name: a file name
+ *
+ * Returns a pointer into @file_name after the root component,
+ * i.e. after the "/" in UNIX or "C:\" under Windows. If @file_name
+ * is not an absolute path it returns %NULL.
+ *
+ * Returns: a pointer into @file_name after the root component
+ */
+const gchar *
+g_path_skip_root (const gchar *file_name)
+{
+ g_return_val_if_fail (file_name != NULL, NULL);
+
+#ifdef G_PLATFORM_WIN32
+ /* Skip \\server\share or //server/share */
+ if (G_IS_DIR_SEPARATOR (file_name[0]) &&
+ G_IS_DIR_SEPARATOR (file_name[1]) &&
+ file_name[2] &&
+ !G_IS_DIR_SEPARATOR (file_name[2]))
{
- if (size < KIBIBYTE_FACTOR)
- {
- g_string_printf (string,
- g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes", (guint) size),
- (guint) size);
- flags &= ~G_FORMAT_SIZE_LONG_FORMAT;
- }
+ gchar *p;
+ p = strchr (file_name + 2, G_DIR_SEPARATOR);
- else if (size < MEBIBYTE_FACTOR)
- g_string_printf (string, _("%.1f KiB"), (gdouble) size / (gdouble) KIBIBYTE_FACTOR);
+#ifdef G_OS_WIN32
+ {
+ gchar *q;
- else if (size < GIBIBYTE_FACTOR)
- g_string_printf (string, _("%.1f MiB"), (gdouble) size / (gdouble) MEBIBYTE_FACTOR);
+ q = strchr (file_name + 2, '/');
+ if (p == NULL || (q != NULL && q < p))
+ p = q;
+ }
+#endif
- else if (size < TEBIBYTE_FACTOR)
- g_string_printf (string, _("%.1f GiB"), (gdouble) size / (gdouble) GIBIBYTE_FACTOR);
+ if (p && p > file_name + 2 && p[1])
+ {
+ file_name = p + 1;
- else if (size < PEBIBYTE_FACTOR)
- g_string_printf (string, _("%.1f TiB"), (gdouble) size / (gdouble) TEBIBYTE_FACTOR);
+ while (file_name[0] && !G_IS_DIR_SEPARATOR (file_name[0]))
+ file_name++;
- else if (size < EXBIBYTE_FACTOR)
- g_string_printf (string, _("%.1f PiB"), (gdouble) size / (gdouble) PEBIBYTE_FACTOR);
+ /* Possibly skip a backslash after the share name */
+ if (G_IS_DIR_SEPARATOR (file_name[0]))
+ file_name++;
- else
- g_string_printf (string, _("%.1f EiB"), (gdouble) size / (gdouble) EXBIBYTE_FACTOR);
+ return (gchar *)file_name;
+ }
}
- else
+#endif
+
+ /* Skip initial slashes */
+ if (G_IS_DIR_SEPARATOR (file_name[0]))
{
- if (size < KILOBYTE_FACTOR)
- {
- g_string_printf (string,
- g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes", (guint) size),
- (guint) size);
- flags &= ~G_FORMAT_SIZE_LONG_FORMAT;
- }
+ while (G_IS_DIR_SEPARATOR (file_name[0]))
+ file_name++;
+ return (gchar *)file_name;
+ }
- else if (size < MEGABYTE_FACTOR)
- g_string_printf (string, _("%.1f kB"), (gdouble) size / (gdouble) KILOBYTE_FACTOR);
+#ifdef G_OS_WIN32
+ /* Skip X:\ */
+ if (g_ascii_isalpha (file_name[0]) &&
+ file_name[1] == ':' &&
+ G_IS_DIR_SEPARATOR (file_name[2]))
+ return (gchar *)file_name + 3;
+#endif
- else if (size < GIGABYTE_FACTOR)
- g_string_printf (string, _("%.1f MB"), (gdouble) size / (gdouble) MEGABYTE_FACTOR);
+ return NULL;
+}
- else if (size < TERABYTE_FACTOR)
- g_string_printf (string, _("%.1f GB"), (gdouble) size / (gdouble) GIGABYTE_FACTOR);
+/**
+ * g_basename:
+ * @file_name: the name of the file
+ *
+ * Gets the name of the file without any leading directory
+ * components. It returns a pointer into the given file name
+ * string.
+ *
+ * Returns: the name of the file without any leading
+ * directory components
+ *
+ * Deprecated:2.2: Use g_path_get_basename() instead, but notice
+ * that g_path_get_basename() allocates new memory for the
+ * returned string, unlike this function which returns a pointer
+ * into the argument.
+ */
+const gchar *
+g_basename (const gchar *file_name)
+{
+ gchar *base;
- else if (size < PETABYTE_FACTOR)
- g_string_printf (string, _("%.1f TB"), (gdouble) size / (gdouble) TERABYTE_FACTOR);
+ g_return_val_if_fail (file_name != NULL, NULL);
- else if (size < EXABYTE_FACTOR)
- g_string_printf (string, _("%.1f PB"), (gdouble) size / (gdouble) PETABYTE_FACTOR);
+ base = strrchr (file_name, G_DIR_SEPARATOR);
- else
- g_string_printf (string, _("%.1f EB"), (gdouble) size / (gdouble) EXABYTE_FACTOR);
- }
+#ifdef G_OS_WIN32
+ {
+ gchar *q;
+ q = strrchr (file_name, '/');
+ if (base == NULL || (q != NULL && q > base))
+ base = q;
+ }
+#endif
- if (flags & G_FORMAT_SIZE_LONG_FORMAT)
- {
- /* First problem: we need to use the number of bytes to decide on
- * the plural form that is used for display, but the number of
- * bytes potentially exceeds the size of a guint (which is what
- * ngettext() takes).
- *
- * From a pragmatic standpoint, it seems that all known languages
- * base plural forms on one or both of the following:
- *
- * - the lowest digits of the number
- *
- * - if the number if greater than some small value
- *
- * Here's how we fake it: Draw an arbitrary line at one thousand.
- * If the number is below that, then fine. If it is above it,
- * then we take the modulus of the number by one thousand (in
- * order to keep the lowest digits) and add one thousand to that
- * (in order to ensure that 1001 is not treated the same as 1).
- */
- guint plural_form = size < 1000 ? size : size % 1000 + 1000;
-
- /* Second problem: we need to translate the string "%u byte" and
- * "%u bytes" for pluralisation, but the correct number format to
- * use for a gsize is different depending on which architecture
- * we're on.
- *
- * Solution: format the number separately and use "%s bytes" on
- * all platforms.
- */
- const gchar *translated_format;
- gchar *formatted_number;
+ if (base)
+ return base + 1;
- /* Translators: the %s in "%s bytes" will always be replaced by a number. */
- translated_format = g_dngettext(GETTEXT_PACKAGE, "%s byte", "%s bytes", plural_form);
+#ifdef G_OS_WIN32
+ if (g_ascii_isalpha (file_name[0]) && file_name[1] == ':')
+ return (gchar*) file_name + 2;
+#endif
- formatted_number = g_strdup_printf ("%'"G_GUINT64_FORMAT, size);
- g_string_append (string, " (");
- g_string_append_printf (string, translated_format, formatted_number);
- g_free (formatted_number);
- g_string_append (string, ")");
- }
+ return (gchar*) file_name;
+}
+
+/**
+ * g_path_get_basename:
+ * @file_name: the name of the file
+ *
+ * Gets the last component of the filename.
+ *
+ * If @file_name ends with a directory separator it gets the component
+ * before the last slash. If @file_name consists only of directory
+ * separators (and on Windows, possibly a drive letter), a single
+ * separator is returned. If @file_name is empty, it gets ".".
+ *
+ * Returns: a newly allocated string containing the last
+ * component of the filename
+ */
+gchar *
+g_path_get_basename (const gchar *file_name)
+{
+ gssize base;
+ gssize last_nonslash;
+ gsize len;
+ gchar *retval;
+
+ g_return_val_if_fail (file_name != NULL, NULL);
+
+ if (file_name[0] == '\0')
+ return g_strdup (".");
+
+ last_nonslash = strlen (file_name) - 1;
+
+ while (last_nonslash >= 0 && G_IS_DIR_SEPARATOR (file_name [last_nonslash]))
+ last_nonslash--;
+
+ if (last_nonslash == -1)
+ /* string only containing slashes */
+ return g_strdup (G_DIR_SEPARATOR_S);
+
+#ifdef G_OS_WIN32
+ if (last_nonslash == 1 &&
+ g_ascii_isalpha (file_name[0]) &&
+ file_name[1] == ':')
+ /* string only containing slashes and a drive */
+ return g_strdup (G_DIR_SEPARATOR_S);
+#endif
+ base = last_nonslash;
+
+ while (base >=0 && !G_IS_DIR_SEPARATOR (file_name [base]))
+ base--;
+
+#ifdef G_OS_WIN32
+ if (base == -1 &&
+ g_ascii_isalpha (file_name[0]) &&
+ file_name[1] == ':')
+ base = 1;
+#endif /* G_OS_WIN32 */
+
+ len = last_nonslash - base;
+ retval = g_malloc (len + 1);
+ memcpy (retval, file_name + base + 1, len);
+ retval [len] = '\0';
- return g_string_free (string, FALSE);
+ return retval;
}
/**
- * g_format_size_for_display:
- * @size: a size in bytes.
- *
- * Formats a size (for example the size of a file) into a human readable string.
- * Sizes are rounded to the nearest size prefix (KB, MB, GB) and are displayed
- * rounded to the nearest tenth. E.g. the file size 3292528 bytes will be
- * converted into the string "3.1 MB".
+ * g_dirname:
+ * @file_name: the name of the file
*
- * The prefix units base is 1024 (i.e. 1 KB is 1024 bytes).
+ * Gets the directory components of a file name.
*
- * This string should be freed with g_free() when not needed any longer.
+ * If the file name has no directory components "." is returned.
+ * The returned string should be freed when no longer needed.
*
- * Returns: a newly-allocated formatted string containing a human readable
- * file size.
+ * Returns: the directory components of the file
*
- * Deprecated:2.30: This function is broken due to its use of SI
- * suffixes to denote IEC units. Use g_format_size()
- * instead.
- * Since: 2.16
- **/
-char *
-g_format_size_for_display (goffset size)
+ * Deprecated: use g_path_get_dirname() instead
+ */
+
+/**
+ * g_path_get_dirname:
+ * @file_name: the name of the file
+ *
+ * Gets the directory components of a file name.
+ *
+ * If the file name has no directory components "." is returned.
+ * The returned string should be freed when no longer needed.
+ *
+ * Returns: the directory components of the file
+ */
+gchar *
+g_path_get_dirname (const gchar *file_name)
{
- if (size < (goffset) KIBIBYTE_FACTOR)
- return g_strdup_printf (g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes",(guint) size), (guint) size);
- else
+ gchar *base;
+ gsize len;
+
+ g_return_val_if_fail (file_name != NULL, NULL);
+
+ base = strrchr (file_name, G_DIR_SEPARATOR);
+
+#ifdef G_OS_WIN32
+ {
+ gchar *q;
+ q = strrchr (file_name, '/');
+ if (base == NULL || (q != NULL && q > base))
+ base = q;
+ }
+#endif
+
+ if (!base)
{
- gdouble displayed_size;
-
- if (size < (goffset) MEBIBYTE_FACTOR)
- {
- displayed_size = (gdouble) size / (gdouble) KIBIBYTE_FACTOR;
- return g_strdup_printf (_("%.1f KB"), displayed_size);
- }
- else if (size < (goffset) GIBIBYTE_FACTOR)
- {
- displayed_size = (gdouble) size / (gdouble) MEBIBYTE_FACTOR;
- return g_strdup_printf (_("%.1f MB"), displayed_size);
- }
- else if (size < (goffset) TEBIBYTE_FACTOR)
- {
- displayed_size = (gdouble) size / (gdouble) GIBIBYTE_FACTOR;
- return g_strdup_printf (_("%.1f GB"), displayed_size);
- }
- else if (size < (goffset) PEBIBYTE_FACTOR)
- {
- displayed_size = (gdouble) size / (gdouble) TEBIBYTE_FACTOR;
- return g_strdup_printf (_("%.1f TB"), displayed_size);
- }
- else if (size < (goffset) EXBIBYTE_FACTOR)
- {
- displayed_size = (gdouble) size / (gdouble) PEBIBYTE_FACTOR;
- return g_strdup_printf (_("%.1f PB"), displayed_size);
- }
- else
+#ifdef G_OS_WIN32
+ if (g_ascii_isalpha (file_name[0]) && file_name[1] == ':')
+ {
+ gchar drive_colon_dot[4];
+
+ drive_colon_dot[0] = file_name[0];
+ drive_colon_dot[1] = ':';
+ drive_colon_dot[2] = '.';
+ drive_colon_dot[3] = '\0';
+
+ return g_strdup (drive_colon_dot);
+ }
+#endif
+ return g_strdup (".");
+ }
+
+ while (base > file_name && G_IS_DIR_SEPARATOR (*base))
+ base--;
+
+#ifdef G_OS_WIN32
+ /* base points to the char before the last slash.
+ *
+ * In case file_name is the root of a drive (X:\) or a child of the
+ * root of a drive (X:\foo), include the slash.
+ *
+ * In case file_name is the root share of an UNC path
+ * (\\server\share), add a slash, returning \\server\share\ .
+ *
+ * In case file_name is a direct child of a share in an UNC path
+ * (\\server\share\foo), include the slash after the share name,
+ * returning \\server\share\ .
+ */
+ if (base == file_name + 1 &&
+ g_ascii_isalpha (file_name[0]) &&
+ file_name[1] == ':')
+ base++;
+ else if (G_IS_DIR_SEPARATOR (file_name[0]) &&
+ G_IS_DIR_SEPARATOR (file_name[1]) &&
+ file_name[2] &&
+ !G_IS_DIR_SEPARATOR (file_name[2]) &&
+ base >= file_name + 2)
+ {
+ const gchar *p = file_name + 2;
+ while (*p && !G_IS_DIR_SEPARATOR (*p))
+ p++;
+ if (p == base + 1)
+ {
+ len = (guint) strlen (file_name) + 1;
+ base = g_new (gchar, len + 1);
+ strcpy (base, file_name);
+ base[len-1] = G_DIR_SEPARATOR;
+ base[len] = 0;
+ return base;
+ }
+ if (G_IS_DIR_SEPARATOR (*p))
{
- displayed_size = (gdouble) size / (gdouble) EXBIBYTE_FACTOR;
- return g_strdup_printf (_("%.1f EB"), displayed_size);
+ p++;
+ while (*p && !G_IS_DIR_SEPARATOR (*p))
+ p++;
+ if (p == base + 1)
+ base++;
}
}
+#endif
+
+ len = (guint) 1 + base - file_name;
+ base = g_new (gchar, len + 1);
+ memmove (base, file_name, len);
+ base[len] = 0;
+
+ return base;
}
+#if defined(MAXPATHLEN)
+#define G_PATH_LENGTH MAXPATHLEN
+#elif defined(PATH_MAX)
+#define G_PATH_LENGTH PATH_MAX
+#elif defined(_PC_PATH_MAX)
+#define G_PATH_LENGTH sysconf(_PC_PATH_MAX)
+#else
+#define G_PATH_LENGTH 2048
+#endif
/**
- * g_file_read_link:
- * @filename: the symbolic link
- * @error: return location for a #GError
+ * g_get_current_dir:
*
- * Reads the contents of the symbolic link @filename like the POSIX
- * readlink() function. The returned string is in the encoding used
- * for filenames. Use g_filename_to_utf8() to convert it to UTF-8.
+ * Gets the current directory.
*
- * Returns: A newly-allocated string with the contents of the symbolic link,
- * or %NULL if an error occurred.
+ * The returned string should be freed when no longer needed.
+ * The encoding of the returned string is system defined.
+ * On Windows, it is always UTF-8.
*
- * Since: 2.4
+ * Since GLib 2.40, this function will return the value of the "PWD"
+ * environment variable if it is set and it happens to be the same as
+ * the current directory. This can make a difference in the case that
+ * the current directory is the target of a symbolic link.
+ *
+ * Returns: the current directory
*/
gchar *
-g_file_read_link (const gchar *filename,
- GError **error)
+g_get_current_dir (void)
{
-#ifdef HAVE_READLINK
- gchar *buffer;
- guint size;
- gint read_size;
-
- size = 256;
- buffer = g_malloc (size);
-
- while (TRUE)
+#ifdef G_OS_WIN32
+
+ gchar *dir = NULL;
+ wchar_t dummy[2], *wdir;
+ int len;
+
+ len = GetCurrentDirectoryW (2, dummy);
+ wdir = g_new (wchar_t, len);
+
+ if (GetCurrentDirectoryW (len, wdir) == len - 1)
+ dir = g_utf16_to_utf8 (wdir, -1, NULL, NULL, NULL);
+
+ g_free (wdir);
+
+ if (dir == NULL)
+ dir = g_strdup ("\\");
+
+ return dir;
+
+#else
+ const gchar *pwd;
+ gchar *buffer = NULL;
+ gchar *dir = NULL;
+ static gulong max_len = 0;
+ struct stat pwdbuf, dotbuf;
+
+ pwd = g_getenv ("PWD");
+ if (pwd != NULL &&
+ g_stat (".", &dotbuf) == 0 && g_stat (pwd, &pwdbuf) == 0 &&
+ dotbuf.st_dev == pwdbuf.st_dev && dotbuf.st_ino == pwdbuf.st_ino)
+ return g_strdup (pwd);
+
+ if (max_len == 0)
+ max_len = (G_PATH_LENGTH == -1) ? 2048 : G_PATH_LENGTH;
+
+ while (max_len < G_MAXULONG / 2)
{
- read_size = readlink (filename, buffer, size);
- if (read_size < 0) {
- int save_errno = errno;
- gchar *display_filename = g_filename_display_name (filename);
-
- g_free (buffer);
- g_set_error (error,
- G_FILE_ERROR,
- g_file_error_from_errno (save_errno),
- _("Failed to read the symbolic link '%s': %s"),
- display_filename,
- g_strerror (save_errno));
- g_free (display_filename);
-
- return NULL;
- }
-
- if (read_size < size)
- {
- buffer[read_size] = 0;
- return buffer;
- }
-
- size *= 2;
- buffer = g_realloc (buffer, size);
+ g_free (buffer);
+ buffer = g_new (gchar, max_len + 1);
+ *buffer = 0;
+ dir = getcwd (buffer, max_len);
+
+ if (dir || errno != ERANGE)
+ break;
+
+ max_len *= 2;
}
-#else
- g_set_error_literal (error,
- G_FILE_ERROR,
- G_FILE_ERROR_INVAL,
- _("Symbolic links not supported"));
-
- return NULL;
-#endif
+
+ if (!dir || !*buffer)
+ {
+ /* hm, should we g_error() out here?
+ * this can happen if e.g. "./" has mode \0000
+ */
+ buffer[0] = G_DIR_SEPARATOR;
+ buffer[1] = 0;
+ }
+
+ dir = g_strdup (buffer);
+ g_free (buffer);
+
+ return dir;
+
+#endif /* !G_OS_WIN32 */
}
-/* NOTE : Keep this part last to ensure nothing in this file uses the
+
+/* NOTE : Keep this part last to ensure nothing in this file uses thn
* below binary compatibility versions.
*/
#if defined (G_OS_WIN32) && !defined (_WIN64)
#undef g_mkstemp
+static gint
+wrap_libc_open (const gchar *filename,
+ int flags,
+ int mode)
+{
+ return open (filename, flags, mode);
+}
+
gint
g_mkstemp (gchar *tmpl)
{
- char *XXXXXX;
- int count, fd;
- static const char letters[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- static const int NLETTERS = sizeof (letters) - 1;
- glong value;
- GTimeVal tv;
- static int counter = 0;
-
- /* find the last occurrence of 'XXXXXX' */
- XXXXXX = g_strrstr (tmpl, "XXXXXX");
-
- if (!XXXXXX)
- {
- errno = EINVAL;
- return -1;
- }
-
- /* Get some more or less random data. */
- g_get_current_time (&tv);
- value = (tv.tv_usec ^ tv.tv_sec) + counter++;
-
- for (count = 0; count < 100; value += 7777, ++count)
- {
- glong v = value;
-
- /* Fill in the random bits. */
- XXXXXX[0] = letters[v % NLETTERS];
- v /= NLETTERS;
- XXXXXX[1] = letters[v % NLETTERS];
- v /= NLETTERS;
- XXXXXX[2] = letters[v % NLETTERS];
- v /= NLETTERS;
- XXXXXX[3] = letters[v % NLETTERS];
- v /= NLETTERS;
- XXXXXX[4] = letters[v % NLETTERS];
- v /= NLETTERS;
- XXXXXX[5] = letters[v % NLETTERS];
-
- /* This is the backward compatibility system codepage version,
- * thus use normal open().
- */
- fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
-
- if (fd >= 0)
- return fd;
- else if (errno != EEXIST)
- /* Any other error will apply also to other names we might
- * try, and there are 2^32 or so of them, so give up now.
- */
- return -1;
- }
-
- /* We got out of the loop because we ran out of combinations to try. */
- errno = EEXIST;
- return -1;
+ /* This is the backward compatibility system codepage version,
+ * thus use normal open().
+ */
+ return get_tmp_file (tmpl, wrap_libc_open,
+ O_RDWR | O_CREAT | O_EXCL, 0600);
}
#undef g_file_open_tmp
return retval;
}
+#undef g_get_current_dir
+
+gchar *
+g_get_current_dir (void)
+{
+ gchar *utf8_dir = g_get_current_dir_utf8 ();
+ gchar *dir = g_locale_from_utf8 (utf8_dir, -1, NULL, NULL, NULL);
+ g_free (utf8_dir);
+ return dir;
+}
+
#endif
+