mirror of https://gitee.com/openkylin/glib2.0.git
597 lines
14 KiB
C
597 lines
14 KiB
C
/* gpathbuf.c: A mutable path builder
|
|
*
|
|
* SPDX-FileCopyrightText: 2023 Emmanuele Bassi
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gpathbuf.h"
|
|
|
|
#include "garray.h"
|
|
#include "gfileutils.h"
|
|
#include "ghash.h"
|
|
#include "gmessages.h"
|
|
#include "gstrfuncs.h"
|
|
|
|
/**
|
|
* GPathBuf:
|
|
*
|
|
* `GPathBuf` is a helper type that allows you to easily build paths from
|
|
* individual elements, using the platform specific conventions for path
|
|
* separators.
|
|
*
|
|
* ```c
|
|
* g_auto (GPathBuf) path;
|
|
*
|
|
* g_path_buf_init (&path);
|
|
*
|
|
* g_path_buf_push (&path, "usr");
|
|
* g_path_buf_push (&path, "bin");
|
|
* g_path_buf_push (&path, "echo");
|
|
*
|
|
* g_autofree char *echo = g_path_buf_to_path (&path);
|
|
* g_assert_cmpstr (echo, ==, "/usr/bin/echo");
|
|
* ```
|
|
*
|
|
* You can also load a full path and then operate on its components:
|
|
*
|
|
* ```c
|
|
* g_auto (GPathBuf) path;
|
|
*
|
|
* g_path_buf_init_from_path (&path, "/usr/bin/echo");
|
|
*
|
|
* g_path_buf_pop (&path);
|
|
* g_path_buf_push (&path, "sh");
|
|
*
|
|
* g_autofree char *sh = g_path_buf_to_path (&path);
|
|
* g_assert_cmpstr (sh, ==, "/usr/bin/sh");
|
|
* ```
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
|
|
typedef struct {
|
|
/* (nullable) (owned) (element-type filename) */
|
|
GPtrArray *path;
|
|
|
|
/* (nullable) (owned) */
|
|
char *extension;
|
|
|
|
gpointer padding[6];
|
|
} RealPathBuf;
|
|
|
|
G_STATIC_ASSERT (sizeof (GPathBuf) == sizeof (RealPathBuf));
|
|
|
|
#define PATH_BUF(b) ((RealPathBuf *) (b))
|
|
|
|
/**
|
|
* g_path_buf_init:
|
|
* @buf: a path buffer
|
|
*
|
|
* Initializes a `GPathBuf` instance.
|
|
*
|
|
* Returns: (transfer none): the initialized path builder
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
GPathBuf *
|
|
g_path_buf_init (GPathBuf *buf)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
|
|
rbuf->path = NULL;
|
|
rbuf->extension = NULL;
|
|
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_init_from_path:
|
|
* @buf: a path buffer
|
|
* @path: (type filename) (nullable): a file system path
|
|
*
|
|
* Initializes a `GPathBuf` instance with the given path.
|
|
*
|
|
* Returns: (transfer none): the initialized path builder
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
GPathBuf *
|
|
g_path_buf_init_from_path (GPathBuf *buf,
|
|
const char *path)
|
|
{
|
|
g_return_val_if_fail (buf != NULL, NULL);
|
|
g_return_val_if_fail (path == NULL || *path != '\0', NULL);
|
|
|
|
g_path_buf_init (buf);
|
|
|
|
if (path == NULL)
|
|
return buf;
|
|
else
|
|
return g_path_buf_push (buf, path);
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_clear:
|
|
* @buf: a path buffer
|
|
*
|
|
* Clears the contents of the path buffer.
|
|
*
|
|
* This function should be use to free the resources in a stack-allocated
|
|
* `GPathBuf` initialized using g_path_buf_init() or
|
|
* g_path_buf_init_from_path().
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
void
|
|
g_path_buf_clear (GPathBuf *buf)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
|
|
g_return_if_fail (buf != NULL);
|
|
|
|
g_clear_pointer (&rbuf->path, g_ptr_array_unref);
|
|
g_clear_pointer (&rbuf->extension, g_free);
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_clear_to_path:
|
|
* @buf: a path buffer
|
|
*
|
|
* Clears the contents of the path buffer and returns the built path.
|
|
*
|
|
* This function returns `NULL` if the `GPathBuf` is empty.
|
|
*
|
|
* See also: g_path_buf_to_path()
|
|
*
|
|
* Returns: (transfer full) (nullable) (type filename): the built path
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
char *
|
|
g_path_buf_clear_to_path (GPathBuf *buf)
|
|
{
|
|
char *res;
|
|
|
|
g_return_val_if_fail (buf != NULL, NULL);
|
|
|
|
res = g_path_buf_to_path (buf);
|
|
g_path_buf_clear (buf);
|
|
|
|
return g_steal_pointer (&res);
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_new:
|
|
*
|
|
* Allocates a new `GPathBuf`.
|
|
*
|
|
* Returns: (transfer full): the newly allocated path buffer
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
GPathBuf *
|
|
g_path_buf_new (void)
|
|
{
|
|
return g_path_buf_init (g_new (GPathBuf, 1));
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_new_from_path:
|
|
* @path: (type filename) (nullable): the path used to initialize the buffer
|
|
*
|
|
* Allocates a new `GPathBuf` with the given @path.
|
|
*
|
|
* Returns: (transfer full): the newly allocated path buffer
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
GPathBuf *
|
|
g_path_buf_new_from_path (const char *path)
|
|
{
|
|
return g_path_buf_init_from_path (g_new (GPathBuf, 1), path);
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_free:
|
|
* @buf: (transfer full) (not nullable): a path buffer
|
|
*
|
|
* Frees a `GPathBuf` allocated by g_path_buf_new().
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
void
|
|
g_path_buf_free (GPathBuf *buf)
|
|
{
|
|
g_return_if_fail (buf != NULL);
|
|
|
|
g_path_buf_clear (buf);
|
|
g_free (buf);
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_free_to_path:
|
|
* @buf: (transfer full) (not nullable): a path buffer
|
|
*
|
|
* Frees a `GPathBuf` allocated by g_path_buf_new(), and
|
|
* returns the path inside the buffer.
|
|
*
|
|
* This function returns `NULL` if the `GPathBuf` is empty.
|
|
*
|
|
* See also: g_path_buf_to_path()
|
|
*
|
|
* Returns: (transfer full) (nullable) (type filename): the path
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
char *
|
|
g_path_buf_free_to_path (GPathBuf *buf)
|
|
{
|
|
char *res;
|
|
|
|
g_return_val_if_fail (buf != NULL, NULL);
|
|
|
|
res = g_path_buf_clear_to_path (buf);
|
|
g_path_buf_free (buf);
|
|
|
|
return g_steal_pointer (&res);
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_copy:
|
|
* @buf: (not nullable): a path buffer
|
|
*
|
|
* Copies the contents of a path buffer into a new `GPathBuf`.
|
|
*
|
|
* Returns: (transfer full): the newly allocated path buffer
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
GPathBuf *
|
|
g_path_buf_copy (GPathBuf *buf)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
RealPathBuf *rcopy;
|
|
GPathBuf *copy;
|
|
|
|
g_return_val_if_fail (buf != NULL, NULL);
|
|
|
|
copy = g_path_buf_new ();
|
|
rcopy = PATH_BUF (copy);
|
|
|
|
if (rbuf->path != NULL)
|
|
{
|
|
rcopy->path = g_ptr_array_new_null_terminated (rbuf->path->len, g_free, TRUE);
|
|
for (guint i = 0; i < rbuf->path->len; i++)
|
|
{
|
|
const char *p = g_ptr_array_index (rbuf->path, i);
|
|
|
|
if (p != NULL)
|
|
g_ptr_array_add (rcopy->path, g_strdup (p));
|
|
}
|
|
}
|
|
|
|
rcopy->extension = g_strdup (rbuf->extension);
|
|
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_push:
|
|
* @buf: a path buffer
|
|
* @path: (type filename): a path
|
|
*
|
|
* Extends the given path buffer with @path.
|
|
*
|
|
* If @path is absolute, it replaces the current path.
|
|
*
|
|
* If @path contains a directory separator, the buffer is extended by
|
|
* as many elements the path provides.
|
|
*
|
|
* On Windows, both forward slashes and backslashes are treated as
|
|
* directory separators. On other platforms, %G_DIR_SEPARATOR_S is the
|
|
* only directory separator.
|
|
*
|
|
* |[<!-- language="C" -->
|
|
* GPathBuf buf, cmp;
|
|
*
|
|
* g_path_buf_init_from_path (&buf, "/tmp");
|
|
* g_path_buf_push (&buf, ".X11-unix/X0");
|
|
* g_path_buf_init_from_path (&cmp, "/tmp/.X11-unix/X0");
|
|
* g_assert_true (g_path_buf_equal (&buf, &cmp));
|
|
* g_path_buf_clear (&cmp);
|
|
*
|
|
* g_path_buf_push (&buf, "/etc/locale.conf");
|
|
* g_path_buf_init_from_path (&cmp, "/etc/locale.conf");
|
|
* g_assert_true (g_path_buf_equal (&buf, &cmp));
|
|
* g_path_buf_clear (&cmp);
|
|
*
|
|
* g_path_buf_clear (&buf);
|
|
* ]|
|
|
*
|
|
* Returns: (transfer none): the same pointer to @buf, for convenience
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
GPathBuf *
|
|
g_path_buf_push (GPathBuf *buf,
|
|
const char *path)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
|
|
g_return_val_if_fail (buf != NULL, NULL);
|
|
g_return_val_if_fail (path != NULL && *path != '\0', buf);
|
|
|
|
if (g_path_is_absolute (path))
|
|
{
|
|
#ifdef G_OS_WIN32
|
|
char **elements = g_strsplit_set (path, "\\/", -1);
|
|
#else
|
|
char **elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
|
|
#endif
|
|
|
|
#ifdef G_OS_UNIX
|
|
/* strsplit() will add an empty element for the leading root,
|
|
* which will cause the path build to ignore it; to avoid it,
|
|
* we re-inject the root as the first element.
|
|
*
|
|
* The first string is empty, but it's still allocated, so we
|
|
* need to free it to avoid leaking it.
|
|
*/
|
|
g_free (elements[0]);
|
|
elements[0] = g_strdup ("/");
|
|
#endif
|
|
|
|
g_clear_pointer (&rbuf->path, g_ptr_array_unref);
|
|
rbuf->path = g_ptr_array_new_null_terminated (g_strv_length (elements), g_free, TRUE);
|
|
|
|
/* Skip empty elements caused by repeated separators */
|
|
for (guint i = 0; elements[i] != NULL; i++)
|
|
{
|
|
if (*elements[i] != '\0')
|
|
g_ptr_array_add (rbuf->path, g_steal_pointer (&elements[i]));
|
|
else
|
|
g_free (elements[i]);
|
|
}
|
|
|
|
g_free (elements);
|
|
}
|
|
else
|
|
{
|
|
char **elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
|
|
|
|
if (rbuf->path == NULL)
|
|
rbuf->path = g_ptr_array_new_null_terminated (g_strv_length (elements), g_free, TRUE);
|
|
|
|
/* Skip empty elements caused by repeated separators */
|
|
for (guint i = 0; elements[i] != NULL; i++)
|
|
{
|
|
if (*elements[i] != '\0')
|
|
g_ptr_array_add (rbuf->path, g_steal_pointer (&elements[i]));
|
|
else
|
|
g_free (elements[i]);
|
|
}
|
|
|
|
g_free (elements);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_pop:
|
|
* @buf: a path buffer
|
|
*
|
|
* Removes the last element of the path buffer.
|
|
*
|
|
* If there is only one element in the path buffer (for example, `/` on
|
|
* Unix-like operating systems or the drive on Windows systems), it will
|
|
* not be removed and %FALSE will be returned instead.
|
|
*
|
|
* |[<!-- language="C" -->
|
|
* GPathBuf buf, cmp;
|
|
*
|
|
* g_path_buf_init_from_path (&buf, "/bin/sh");
|
|
*
|
|
* g_path_buf_pop (&buf);
|
|
* g_path_buf_init_from_path (&cmp, "/bin");
|
|
* g_assert_true (g_path_buf_equal (&buf, &cmp));
|
|
* g_path_buf_clear (&cmp);
|
|
*
|
|
* g_path_buf_pop (&buf);
|
|
* g_path_buf_init_from_path (&cmp, "/");
|
|
* g_assert_true (g_path_buf_equal (&buf, &cmp));
|
|
* g_path_buf_clear (&cmp);
|
|
*
|
|
* g_path_buf_clear (&buf);
|
|
* ]|
|
|
*
|
|
* Returns: `TRUE` if the buffer was modified and `FALSE` otherwise
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
gboolean
|
|
g_path_buf_pop (GPathBuf *buf)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
|
|
g_return_val_if_fail (buf != NULL, FALSE);
|
|
g_return_val_if_fail (rbuf->path != NULL, FALSE);
|
|
|
|
/* Keep the first element of the buffer; it's either '/' or the drive */
|
|
if (rbuf->path->len > 1)
|
|
{
|
|
g_ptr_array_remove_index (rbuf->path, rbuf->path->len - 1);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_set_filename:
|
|
* @buf: a path buffer
|
|
* @file_name: (type filename) (not nullable): the file name in the path
|
|
*
|
|
* Sets the file name of the path.
|
|
*
|
|
* If the path buffer is empty, the filename is left unset and this
|
|
* function returns `FALSE`.
|
|
*
|
|
* If the path buffer only contains the root element (on Unix-like operating
|
|
* systems) or the drive (on Windows), this is the equivalent of pushing
|
|
* the new @file_name.
|
|
*
|
|
* If the path buffer contains a path, this is the equivalent of
|
|
* popping the path buffer and pushing @file_name, creating a
|
|
* sibling of the original path.
|
|
*
|
|
* |[<!-- language="C" -->
|
|
* GPathBuf buf, cmp;
|
|
*
|
|
* g_path_buf_init_from_path (&buf, "/");
|
|
*
|
|
* g_path_buf_set_filename (&buf, "bar");
|
|
* g_path_buf_init_from_path (&cmp, "/bar");
|
|
* g_assert_true (g_path_buf_equal (&buf, &cmp));
|
|
* g_path_buf_clear (&cmp);
|
|
*
|
|
* g_path_buf_set_filename (&buf, "baz.txt");
|
|
* g_path_buf_init_from_path (&cmp, "/baz.txt");
|
|
* g_assert_true (g_path_buf_equal (&buf, &cmp);
|
|
* g_path_buf_clear (&cmp);
|
|
*
|
|
* g_path_buf_clear (&buf);
|
|
* ]|
|
|
*
|
|
* Returns: `TRUE` if the file name was replaced, and `FALSE` otherwise
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
gboolean
|
|
g_path_buf_set_filename (GPathBuf *buf,
|
|
const char *file_name)
|
|
{
|
|
g_return_val_if_fail (buf != NULL, FALSE);
|
|
g_return_val_if_fail (file_name != NULL, FALSE);
|
|
|
|
if (PATH_BUF (buf)->path == NULL)
|
|
return FALSE;
|
|
|
|
g_path_buf_pop (buf);
|
|
g_path_buf_push (buf, file_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_set_extension:
|
|
* @buf: a path buffer
|
|
* @extension: (type filename) (nullable): the file extension
|
|
*
|
|
* Adds an extension to the file name in the path buffer.
|
|
*
|
|
* If @extension is `NULL`, the extension will be unset.
|
|
*
|
|
* If the path buffer does not have a file name set, this function returns
|
|
* `FALSE` and leaves the path buffer unmodified.
|
|
*
|
|
* Returns: `TRUE` if the extension was replaced, and `FALSE` otherwise
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
gboolean
|
|
g_path_buf_set_extension (GPathBuf *buf,
|
|
const char *extension)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
|
|
g_return_val_if_fail (buf != NULL, FALSE);
|
|
|
|
if (rbuf->path != NULL)
|
|
return g_set_str (&rbuf->extension, extension);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_to_path:
|
|
* @buf: a path buffer
|
|
*
|
|
* Retrieves the built path from the path buffer.
|
|
*
|
|
* On Windows, the result contains backslashes as directory separators,
|
|
* even if forward slashes were used in input.
|
|
*
|
|
* If the path buffer is empty, this function returns `NULL`.
|
|
*
|
|
* Returns: (transfer full) (type filename) (nullable): the path
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
char *
|
|
g_path_buf_to_path (GPathBuf *buf)
|
|
{
|
|
RealPathBuf *rbuf = PATH_BUF (buf);
|
|
char *path = NULL;
|
|
|
|
g_return_val_if_fail (buf != NULL, NULL);
|
|
|
|
if (rbuf->path != NULL)
|
|
path = g_build_filenamev ((char **) rbuf->path->pdata);
|
|
|
|
if (path != NULL && rbuf->extension != NULL)
|
|
{
|
|
char *tmp = g_strconcat (path, ".", rbuf->extension, NULL);
|
|
|
|
g_free (path);
|
|
path = g_steal_pointer (&tmp);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* g_path_buf_equal:
|
|
* @v1: (not nullable): a path buffer to compare
|
|
* @v2: (not nullable): a path buffer to compare
|
|
*
|
|
* Compares two path buffers for equality and returns `TRUE`
|
|
* if they are equal.
|
|
*
|
|
* The path inside the paths buffers are not going to be normalized,
|
|
* so `X/Y/Z/A/..`, `X/./Y/Z` and `X/Y/Z` are not going to be considered
|
|
* equal.
|
|
*
|
|
* This function can be passed to g_hash_table_new() as the
|
|
* `key_equal_func` parameter.
|
|
*
|
|
* Returns: `TRUE` if the two path buffers are equal,
|
|
* and `FALSE` otherwise
|
|
*
|
|
* Since: 2.76
|
|
*/
|
|
gboolean
|
|
g_path_buf_equal (gconstpointer v1,
|
|
gconstpointer v2)
|
|
{
|
|
if (v1 == v2)
|
|
return TRUE;
|
|
|
|
/* We resolve the buffer into a path to normalize its contents;
|
|
* this won't resolve symbolic links or `.` and `..` components
|
|
*/
|
|
char *p1 = g_path_buf_to_path ((GPathBuf *) v1);
|
|
char *p2 = g_path_buf_to_path ((GPathBuf *) v2);
|
|
|
|
gboolean res = p1 != NULL && p2 != NULL
|
|
? g_str_equal (p1, p2)
|
|
: FALSE;
|
|
|
|
g_free (p1);
|
|
g_free (p2);
|
|
|
|
return res;
|
|
}
|