1
0
mirror of https://github.com/weechat/weechat.git synced 2026-07-03 08:13:14 +02:00
Files
weechat/src/core/core-dir.c
T
Aaron Jones f5038bccbc Fix function prototypes for list of arguments
At the moment, building WeeChat triggers several thousand -Wstrict-prototypes
diagnostics.  This is due to its source code using an empty argument list for
functions and function pointers that take no arguments, instead of explicitly
declaring that they take no arguments by using a void list.

This commit replaces all empty argument lists with a void list.

Note that Ruby's headers also suffer the same problem, which WeeChat can't
do anything to fix.  Thus, building WeeChat with the Ruby plugin enabled
will still issue approximately 30 such diagnostics.
2025-03-10 08:16:52 +01:00

1432 lines
34 KiB
C

/*
* core-dir.c - directory/file functions
*
* Copyright (C) 2003-2025 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
/* for P_tmpdir in stdio.h */
#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif
/* for nftw() */
#define _XOPEN_SOURCE 700
#if defined(__APPLE__)
#define _DARWIN_C_SOURCE
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <ftw.h>
#include <zlib.h>
#ifdef HAVE_ZSTD
#include <zstd.h>
#endif
#include "weechat.h"
#include "core-config.h"
#include "core-string.h"
/*
* Returns the path to a temporary directory, the first valid directory in
* this list:
* - content of environment variable "TMPDIR"
* - P_tmpdir (from stdio.h)
* - content of environment variable "HOME" (user home directory)
* - "." (current directory)
*/
char *
dir_get_temp_dir (void)
{
char *tmpdir;
struct stat buf;
int rc;
/* get directory from $TMPDIR */
tmpdir = getenv ("TMPDIR");
if (tmpdir && tmpdir[0])
{
rc = stat (tmpdir, &buf);
if ((rc == 0) && S_ISDIR(buf.st_mode))
return strdup (tmpdir);
}
/* get directory from P_tmpdir */
rc = stat (P_tmpdir, &buf);
if ((rc == 0) && S_ISDIR(buf.st_mode))
return strdup (P_tmpdir);
/* get directory from $HOME */
tmpdir = getenv ("HOME");
if (tmpdir && tmpdir[0])
{
rc = stat (tmpdir, &buf);
if ((rc == 0) && S_ISDIR(buf.st_mode))
return strdup (tmpdir);
}
/* fallback on current directory */
return strdup (".");
}
/*
* Creates a directory in WeeChat home.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_mkdir_home (const char *directory, int mode)
{
char *dir, *dir1, *dir2, *dir3, *dir4, *dir5;
int rc;
rc = 0;
dir = NULL;
dir1 = NULL;
dir2 = NULL;
dir3 = NULL;
dir4 = NULL;
dir5 = NULL;
if (!directory)
goto end;
if (strncmp (directory, "${", 2) == 0)
{
dir = strdup (directory);
}
else
{
/* build directory in data dir by default */
if (string_asprintf (&dir, "%s/%s", weechat_data_dir, directory) < 0)
goto end;
}
dir1 = string_replace (dir, "${weechat_config_dir}", weechat_config_dir);
if (!dir1)
goto end;
dir2 = string_replace (dir1, "${weechat_data_dir}", weechat_data_dir);
if (!dir2)
goto end;
dir3 = string_replace (dir2, "${weechat_state_dir}", weechat_state_dir);
if (!dir3)
goto end;
dir4 = string_replace (dir3, "${weechat_cache_dir}", weechat_cache_dir);
if (!dir4)
goto end;
dir5 = string_replace (dir4, "${weechat_runtime_dir}", weechat_runtime_dir);
if (!dir5)
goto end;
/* build directory, adding WeeChat home */
if (mkdir (dir5, mode) < 0)
{
if (errno != EEXIST)
goto end;
}
rc = 1;
end:
free (dir);
free (dir1);
free (dir2);
free (dir3);
free (dir4);
free (dir5);
return rc;
}
/*
* Creates a directory.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_mkdir (const char *directory, int mode)
{
if (!directory)
return 0;
if (mkdir (directory, mode) < 0)
{
if (errno != EEXIST)
return 0;
}
return 1;
}
/*
* Creates a directory and makes parent directories as needed.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_mkdir_parents (const char *directory, int mode)
{
char *string, *ptr_string, *pos_sep;
struct stat buf;
int rc;
if (!directory)
return 0;
string = strdup (directory);
if (!string)
return 0;
ptr_string = string;
while (ptr_string[0] == DIR_SEPARATOR_CHAR)
{
ptr_string++;
}
while (ptr_string && ptr_string[0])
{
pos_sep = strchr (ptr_string, DIR_SEPARATOR_CHAR);
if (pos_sep)
pos_sep[0] = '\0';
rc = stat (string, &buf);
if ((rc < 0) || !S_ISDIR(buf.st_mode))
{
/* try to create directory */
if (!dir_mkdir (string, mode))
{
free (string);
return 0;
}
}
if (pos_sep)
{
pos_sep[0] = DIR_SEPARATOR_CHAR;
ptr_string = pos_sep + 1;
}
else
ptr_string = NULL;
}
free (string);
return 1;
}
/*
* Unlinks a file or directory; callback called by function dir_rmtree().
*
* Returns the return code of remove():
* 0: OK
* -1: error
*/
int
dir_unlink_cb (const char *fpath, const struct stat *sb, int typeflag,
struct FTW *ftwbuf)
{
/* make C compiler happy */
(void) sb;
(void) typeflag;
(void) ftwbuf;
return remove (fpath);
}
/*
* Removes a directory and all files inside recursively.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_rmtree (const char *directory)
{
int rc;
if (!directory)
return 0;
rc = nftw (directory, dir_unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
return (rc == 0) ? 1 : 0;
}
/*
* Uses one or four different paths for WeeChat home directories.
*
* If 4 paths are given, they must be separated by colons and given in this
* order: config, data, cache, runtime.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_set_home_path (char *path)
{
char **paths;
int rc, num_paths;
rc = 0;
paths = NULL;
if (!path)
goto end;
paths = string_split (path, ":", NULL, 0, 0, &num_paths);
if (!paths)
{
string_fprintf (stderr, _("Error: not enough memory\n"));
goto end;
}
if (num_paths == 1)
{
weechat_config_dir = string_expand_home (paths[0]);
weechat_data_dir = string_expand_home (paths[0]);
weechat_state_dir = string_expand_home (paths[0]);
weechat_cache_dir = string_expand_home (paths[0]);
weechat_runtime_dir = string_expand_home (paths[0]);
}
else if (num_paths == 4)
{
/* compatibility with versions between 3.2 and 4.2.x */
weechat_config_dir = string_expand_home (paths[0]);
weechat_data_dir = string_expand_home (paths[1]);
/* state dir = data dir by default */
weechat_state_dir = string_expand_home (paths[1]);
weechat_cache_dir = string_expand_home (paths[2]);
weechat_runtime_dir = string_expand_home (paths[3]);
}
else if (num_paths == 5)
{
weechat_config_dir = string_expand_home (paths[0]);
weechat_data_dir = string_expand_home (paths[1]);
weechat_state_dir = string_expand_home (paths[2]);
weechat_cache_dir = string_expand_home (paths[3]);
weechat_runtime_dir = string_expand_home (paths[4]);
}
else
{
/*
* value of 4 is not mentioned in the message because it's kept only
* for compatibility with old releases, it should not be used anymore
*/
string_fprintf (stderr,
_("Error: wrong number of paths for home directories "
"(expected: 1 or 5, received: %d)\n"),
num_paths);
goto end;
}
rc = 1;
end:
string_free_split (paths);
return rc;
}
/*
* Creates WeeChat temporary home directory (deleted on exit).
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_create_home_temp_dir (void)
{
char *temp_dir, *temp_home_template, *ptr_weechat_home;
int rc, add_separator;
rc = 0;
temp_dir = NULL;
temp_home_template = NULL;
temp_dir = dir_get_temp_dir ();
if (!temp_dir || !temp_dir[0])
goto memory_error;
add_separator = (temp_dir[strlen (temp_dir) - 1] != DIR_SEPARATOR_CHAR);
if (string_asprintf (&temp_home_template,
"%s%sweechat_temp_XXXXXX",
temp_dir,
(add_separator) ? DIR_SEPARATOR : "") < 0)
{
goto memory_error;
}
ptr_weechat_home = mkdtemp (temp_home_template);
if (!ptr_weechat_home)
{
string_fprintf (stderr,
_("Error: unable to create a temporary "
"home directory (using template: \"%s\")\n"),
temp_home_template);
goto end;
}
weechat_config_dir = strdup (ptr_weechat_home);
weechat_data_dir = strdup (ptr_weechat_home);
weechat_state_dir = strdup (ptr_weechat_home);
weechat_cache_dir = strdup (ptr_weechat_home);
weechat_runtime_dir = strdup (ptr_weechat_home);
weechat_home_delete_on_exit = 1;
rc = 1;
goto end;
memory_error:
string_fprintf (stderr, _("Error: not enough memory\n"));
end:
free (temp_dir);
free (temp_home_template);
return rc;
}
/*
* Finds XDG directories.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_find_xdg_dirs (char **config_dir, char **data_dir, char **state_dir,
char **cache_dir, char **runtime_dir)
{
char *ptr_home, path[PATH_MAX];
char *xdg_config_home, *xdg_data_home, *xdg_state_home, *xdg_cache_home;
char *xdg_runtime_dir;
*config_dir = NULL;
*data_dir = NULL;
*state_dir = NULL;
*cache_dir = NULL;
*runtime_dir = NULL;
ptr_home = getenv ("HOME");
if (!ptr_home)
goto error_home;
xdg_config_home = getenv ("XDG_CONFIG_HOME");
xdg_data_home = getenv ("XDG_DATA_HOME");
xdg_state_home = getenv ("XDG_STATE_HOME");
xdg_cache_home = getenv ("XDG_CACHE_HOME");
xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR");
/* set config dir: $XDG_CONFIG_HOME/weechat or $HOME/.config/weechat */
if (xdg_config_home && xdg_config_home[0])
{
snprintf (path, sizeof (path),
"%s%s%s",
xdg_config_home, DIR_SEPARATOR, "weechat");
}
else
{
snprintf (path, sizeof (path),
"%s%s%s%s%s",
ptr_home, DIR_SEPARATOR, ".config", DIR_SEPARATOR, "weechat");
}
*config_dir = strdup (path);
if (!*config_dir)
goto error_memory;
/* set data dir: $XDG_DATA_HOME/weechat or $HOME/.local/share/weechat */
if (xdg_data_home && xdg_data_home[0])
{
snprintf (path, sizeof (path),
"%s%s%s",
xdg_data_home, DIR_SEPARATOR, "weechat");
}
else
{
snprintf (path, sizeof (path),
"%s%s%s%s%s%s%s",
ptr_home, DIR_SEPARATOR, ".local", DIR_SEPARATOR, "share",
DIR_SEPARATOR, "weechat");
}
*data_dir = strdup (path);
if (!*data_dir)
goto error_memory;
/* set state dir: $XDG_STATE_HOME/weechat or $HOME/.local/state/weechat */
if (xdg_state_home && xdg_state_home[0])
{
snprintf (path, sizeof (path),
"%s%s%s",
xdg_state_home, DIR_SEPARATOR, "weechat");
}
else
{
snprintf (path, sizeof (path),
"%s%s%s%s%s%s%s",
ptr_home, DIR_SEPARATOR, ".local", DIR_SEPARATOR, "state",
DIR_SEPARATOR, "weechat");
}
*state_dir = strdup (path);
if (!*state_dir)
goto error_memory;
/* set cache dir: $XDG_CACHE_HOME/weechat or $HOME/.cache/weechat */
if (xdg_cache_home && xdg_cache_home[0])
{
snprintf (path, sizeof (path),
"%s%s%s",
xdg_cache_home, DIR_SEPARATOR, "weechat");
}
else
{
snprintf (path, sizeof (path),
"%s%s%s%s%s",
ptr_home, DIR_SEPARATOR, ".cache", DIR_SEPARATOR, "weechat");
}
*cache_dir = strdup (path);
if (!*cache_dir)
goto error_memory;
/* set runtime dir: $XDG_RUNTIME_DIR/weechat or same as cache dir */
if (xdg_runtime_dir && xdg_runtime_dir[0])
{
snprintf (path, sizeof (path),
"%s%s%s",
xdg_runtime_dir, DIR_SEPARATOR, "weechat");
*runtime_dir = strdup (path);
}
else
{
*runtime_dir = strdup (*cache_dir);
}
if (!*runtime_dir)
goto error_memory;
return 1;
error_home:
string_fprintf (stderr,
_("Error: environment variable \"HOME\" is not defined\n"));
goto error;
error_memory:
string_fprintf (stderr, _("Error: not enough memory\n"));
goto error;
error:
if (*config_dir)
{
free (*config_dir);
*config_dir = NULL;
}
if (*data_dir)
{
free (*data_dir);
*data_dir = NULL;
}
if (*state_dir)
{
free (*state_dir);
*state_dir = NULL;
}
if (*cache_dir)
{
free (*cache_dir);
*cache_dir = NULL;
}
if (*runtime_dir)
{
free (*runtime_dir);
*runtime_dir = NULL;
}
return 0;
}
/*
* Finds WeeChat home directories: it can be either XDG directories or the
* same directory for all files (like the legacy directory ~/.weechat).
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_find_home_dirs (void)
{
char *ptr_home, *ptr_weechat_home, *config_weechat_home;
char *config_dir, *data_dir, *state_dir, *cache_dir, *runtime_dir;
char path[PATH_MAX];
/* temporary WeeChat home */
if (weechat_home_temp)
return dir_create_home_temp_dir ();
/* use a forced home with -d/--dir */
if (weechat_home_force)
return dir_set_home_path (weechat_home_force);
/* use environment variable "WEECHAT_HOME" (if set) */
ptr_weechat_home = getenv ("WEECHAT_HOME");
if (ptr_weechat_home && ptr_weechat_home[0])
return dir_set_home_path (ptr_weechat_home);
/* use the home forced at compilation time (if set) */
config_weechat_home = WEECHAT_HOME;
if (config_weechat_home[0])
return dir_set_home_path (config_weechat_home);
if (!dir_find_xdg_dirs (&config_dir, &data_dir, &state_dir, &cache_dir,
&runtime_dir))
return 0;
/* check if {weechat_config_dir}/weechat.conf exists */
snprintf (path, sizeof (path),
"%s%s%s",
config_dir, DIR_SEPARATOR, "weechat.conf");
if (access (path, F_OK) == 0)
goto use_xdg;
/*
* check if $HOME/.weechat/weechat.conf exists
* (compatibility with old releases not supporting XDG directories)
*/
ptr_home = getenv ("HOME");
snprintf (path, sizeof (path),
"%s%s%s%s%s",
ptr_home, DIR_SEPARATOR, ".weechat", DIR_SEPARATOR, "weechat.conf");
if (access (path, F_OK) == 0)
{
snprintf (path, sizeof (path),
"%s%s%s",
ptr_home, DIR_SEPARATOR, ".weechat");
weechat_config_dir = strdup (path);
weechat_data_dir = strdup (path);
weechat_state_dir = strdup (path);
weechat_cache_dir = strdup (path);
weechat_runtime_dir = strdup (path);
free (config_dir);
free (data_dir);
free (state_dir);
free (cache_dir);
free (runtime_dir);
return 1;
}
/* use XDG directories */
use_xdg:
weechat_config_dir = config_dir;
weechat_data_dir = data_dir;
weechat_state_dir = state_dir;
weechat_cache_dir = cache_dir;
weechat_runtime_dir = runtime_dir;
return 1;
}
/*
* Removes trailing separators in path, which is modified in place
* (each trailing separator is replaced by NUL char).
*
* Example on Linux: "/home/xxx/" => "/home/xxx".
*/
void
dir_remove_trailing_separators (char *path)
{
int length;
if (!path || !path[0])
return;
length = strlen (path);
while ((length > 1) && (path[length - 1] == DIR_SEPARATOR_CHAR))
{
path[length - 1] = '\0';
length--;
}
}
/*
* Creates a home directory.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_create_home_dir (char *path)
{
struct stat statinfo;
/* if home already exists, it has to be a directory */
if (stat (path, &statinfo) == 0)
{
if (!S_ISDIR (statinfo.st_mode))
{
string_fprintf (stderr,
_("Error: \"%s\" is not a directory\n"),
path);
return 0;
}
}
/* create home directory; error is fatal */
if (!dir_mkdir_parents (path, 0700))
{
string_fprintf (stderr,
_("Error: cannot create directory \"%s\"\n"),
path);
return 0;
}
return 1;
}
/*
* Creates WeeChat home directories.
*
* Any error in this function (or a sub function called) is fatal: WeeChat
* cannot run at all without the home directories.
*/
void
dir_create_home_dirs (void)
{
int rc;
if (!dir_find_home_dirs ())
goto error;
dir_remove_trailing_separators (weechat_config_dir);
dir_remove_trailing_separators (weechat_data_dir);
dir_remove_trailing_separators (weechat_state_dir);
dir_remove_trailing_separators (weechat_cache_dir);
dir_remove_trailing_separators (weechat_runtime_dir);
rc = dir_create_home_dir (weechat_config_dir);
if (rc && (strcmp (weechat_config_dir, weechat_data_dir) != 0))
rc = dir_create_home_dir (weechat_data_dir);
if (rc && (strcmp (weechat_config_dir, weechat_state_dir) != 0))
rc = dir_create_home_dir (weechat_state_dir);
if (rc && (strcmp (weechat_config_dir, weechat_cache_dir) != 0))
rc = dir_create_home_dir (weechat_cache_dir);
if (rc && (strcmp (weechat_config_dir, weechat_runtime_dir) != 0))
rc = dir_create_home_dir (weechat_runtime_dir);
if (rc)
return;
error:
weechat_shutdown (EXIT_FAILURE, 0);
}
/*
* Removes WeeChat home directories (called when -t / --temp-dir is given).
*/
void
dir_remove_home_dirs (void)
{
dir_rmtree (weechat_config_dir);
if (strcmp (weechat_config_dir, weechat_data_dir) != 0)
dir_rmtree (weechat_data_dir);
if (strcmp (weechat_config_dir, weechat_state_dir) != 0)
dir_rmtree (weechat_state_dir);
if (strcmp (weechat_config_dir, weechat_cache_dir) != 0)
dir_rmtree (weechat_cache_dir);
if (strcmp (weechat_config_dir, weechat_runtime_dir) != 0)
dir_rmtree (weechat_runtime_dir);
}
/*
* Returns a string with home directories separated by colons, in this order:
* config_dir, data_dir, state_dir, cache_dir, runtime_dir.
*
* Example of value returned:
* /home/user/.config/weechat:/home/user/.local/share/weechat:
* /home/user/.local/state/weechat:/home/user/.cache/weechat:
* /run/user/1000/weechat
*
* Note: result must be freed after use.
*/
char *
dir_get_string_home_dirs (void)
{
char *dirs[6];
dirs[0] = weechat_config_dir;
dirs[1] = weechat_data_dir;
dirs[2] = weechat_state_dir;
dirs[3] = weechat_cache_dir;
dirs[4] = weechat_runtime_dir;
dirs[5] = NULL;
return string_rebuild_split_string ((const char **)dirs, ":", 0, -1);
}
/*
* Finds files in a directory and executes a function on each file.
*/
void
dir_exec_on_files (const char *directory, int recurse_subdirs,
int hidden_files,
void (*callback)(void *data, const char *filename),
void *callback_data)
{
char complete_filename[PATH_MAX];
DIR *dir;
struct dirent *entry;
struct stat statbuf;
if (!directory || !callback)
return;
dir = opendir (directory);
if (dir)
{
while ((entry = readdir (dir)))
{
if (hidden_files || (entry->d_name[0] != '.'))
{
snprintf (complete_filename, sizeof (complete_filename),
"%s/%s", directory, entry->d_name);
lstat (complete_filename, &statbuf);
if (S_ISDIR(statbuf.st_mode))
{
if (recurse_subdirs
&& (strcmp (entry->d_name, ".") != 0)
&& (strcmp (entry->d_name, "..") != 0))
{
dir_exec_on_files (complete_filename, 1, hidden_files,
callback, callback_data);
}
}
else
{
(*callback) (callback_data, complete_filename);
}
}
}
closedir (dir);
}
}
/*
* Searches for the full name of a WeeChat library with name and extension
* (searches first in WeeChat user's dir, then WeeChat global lib directory).
*
* Returns name of library found, NULL if not found.
*
* Note: result must be freed after use (if not NULL).
*/
char *
dir_search_full_lib_name_ext (const char *filename, const char *extension,
const char *plugins_dir)
{
char name_with_ext[256], final_name[PATH_MAX], *extra_libdir;
struct stat st;
int rc;
rc = snprintf (name_with_ext, sizeof (name_with_ext),
"%s%s",
filename,
(strchr (filename, '.')) ? "" : extension);
if ((rc < 0) || (rc >= (int)sizeof (name_with_ext)))
return NULL;
/* try libdir from environment variable WEECHAT_EXTRA_LIBDIR */
extra_libdir = getenv (WEECHAT_EXTRA_LIBDIR);
if (extra_libdir && extra_libdir[0])
{
rc = snprintf (final_name, sizeof (final_name),
"%s%s%s%s%s",
extra_libdir,
DIR_SEPARATOR,
plugins_dir,
DIR_SEPARATOR,
name_with_ext);
if ((rc < 0) || (rc >= (int)sizeof (final_name)))
return NULL;
if ((stat (final_name, &st) == 0) && (st.st_size > 0))
return strdup (final_name);
}
/* try WeeChat user's dir */
rc = snprintf (final_name, sizeof (final_name),
"%s%s%s%s%s",
weechat_data_dir,
DIR_SEPARATOR,
plugins_dir,
DIR_SEPARATOR,
name_with_ext);
if ((rc < 0) || (rc >= (int)sizeof (final_name)))
return NULL;
if ((stat (final_name, &st) == 0) && (st.st_size > 0))
return strdup (final_name);
/* try WeeChat global lib dir */
rc = snprintf (final_name, sizeof (final_name),
"%s%s%s%s%s",
WEECHAT_LIBDIR,
DIR_SEPARATOR,
plugins_dir,
DIR_SEPARATOR,
name_with_ext);
if ((rc < 0) || (rc >= (int)sizeof (final_name)))
return NULL;
if ((stat (final_name, &st) == 0) && (st.st_size > 0))
return strdup (final_name);
return NULL;
}
/*
* Searches for the full name of a WeeChat library with name.
*
* All extensions listed in option "weechat.plugin.extension" are tested.
*
* Note: result must be freed after use (if not NULL).
*/
char *
dir_search_full_lib_name (const char *filename, const char *plugins_dir)
{
char *filename2, *full_name;
int i;
/* expand home in filename */
filename2 = string_expand_home (filename);
if (!filename2)
return NULL;
/* if full path, return it */
if (strchr (filename2, '/') || strchr (filename2, '\\'))
return filename2;
if (config_plugin_extensions)
{
for (i = 0; i < config_num_plugin_extensions; i++)
{
full_name = dir_search_full_lib_name_ext (
filename2,
config_plugin_extensions[i],
plugins_dir);
if (full_name)
{
free (filename2);
return full_name;
}
}
}
else
{
full_name = dir_search_full_lib_name_ext (filename2, "", plugins_dir);
if (full_name)
{
free (filename2);
return full_name;
}
}
free (filename2);
return strdup (filename);
}
/*
* Reads content of a file.
*
* Returns an allocated buffer with the content of file, NULL if error.
*
* Note: result must be freed after use.
*/
char *
dir_file_get_content (const char *filename)
{
char *buffer, *buffer2;
FILE *f;
size_t count, fp;
if (!filename)
return NULL;
buffer = NULL;
fp = 0;
f = fopen (filename, "r");
if (!f)
goto error;
while (!feof (f))
{
if (fp > SIZE_MAX - (1024 * sizeof (char)))
goto error;
buffer2 = (char *) realloc (buffer, (fp + (1024 * sizeof (char))));
if (!buffer2)
goto error;
buffer = buffer2;
count = fread (&buffer[fp], sizeof (char), 1024, f);
if (count <= 0)
goto error;
fp += count;
}
if (fp > SIZE_MAX - sizeof (char))
goto error;
buffer2 = (char *) realloc (buffer, fp + sizeof (char));
if (!buffer2)
goto error;
buffer = buffer2;
buffer[fp] = '\0';
fclose (f);
return buffer;
error:
free (buffer);
if (f)
fclose (f);
return NULL;
}
/*
* Copies a file to another location.
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_file_copy (const char *from, const char *to)
{
FILE *src, *dst;
char *buffer;
int rc;
size_t count;
rc = 0;
buffer = NULL;
src = NULL;
dst = NULL;
if (!from || !from[0] || !to || !to[0])
goto end;
buffer = malloc (65536);
if (!buffer)
goto end;
src = fopen (from, "rb");
if (!src)
goto end;
dst = fopen (to, "wb");
if (!dst)
goto end;
while (!feof (src))
{
count = fread (buffer, 1, 65536, src);
if (count <= 0)
goto end;
if (fwrite (buffer, 1, count, dst) <= 0)
goto end;
}
rc = 1;
end:
free (buffer);
if (src)
fclose (src);
if (dst)
fclose (dst);
return rc;
}
/*
* Compresses a file with gzip.
*
* Notes:
* - the output file must not exist
* - compression_level is an integer between 1 and 9
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_file_compress_gzip (const char *from, const char *to,
int compression_level)
{
FILE *source, *dest;
z_stream strm;
unsigned char *buffer_in, *buffer_out;
int rc, ret, flush;
size_t buffer_size, have;
source = NULL;
dest = NULL;
buffer_size = 256 * 1024;
buffer_in = NULL;
buffer_out = NULL;
rc = 0;
if (!from || !to || (compression_level < 1) || (compression_level > 9))
goto end;
buffer_in = malloc (buffer_size);
if (!buffer_in)
goto end;
buffer_out = malloc (buffer_size);
if (!buffer_out)
goto end;
source = fopen (from, "rb");
if (!source)
goto end;
dest = fopen (to, "wbx");
if (!dest)
goto end;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit2 (
&strm,
compression_level,
Z_DEFLATED, /* method */
15 + 16, /* + 16 = gzip instead of zlib */
8, /* memLevel */
Z_DEFAULT_STRATEGY); /* strategy */
if (ret != Z_OK)
goto end;
do
{
strm.avail_in = fread (buffer_in, 1, buffer_size, source);
if (ferror (source))
goto error;
flush = feof (source) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = buffer_in;
do
{
strm.avail_out = buffer_size;
strm.next_out = buffer_out;
ret = deflate (&strm, flush);
if (ret == Z_STREAM_ERROR)
goto error;
have = buffer_size - strm.avail_out;
if ((fwrite (buffer_out, 1, have, dest) != have) || ferror (dest))
goto error;
} while (strm.avail_out == 0);
if (strm.avail_in != 0)
goto error;
} while (flush != Z_FINISH);
if (ret != Z_STREAM_END)
goto error;
(void) deflateEnd (&strm);
rc = 1;
goto end;
error:
(void) deflateEnd (&strm);
unlink (to);
end:
free (buffer_in);
free (buffer_out);
if (source)
fclose (source);
if (dest)
fclose (dest);
return rc;
}
/*
* Compresses a file with zstandard.
*
* Notes:
* - the output file must not exist
* - compression_level is an integer between 1 and 19
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_file_compress_zstd (const char *from, const char *to,
int compression_level)
{
#ifdef HAVE_ZSTD
FILE *source, *dest;
void *buffer_in, *buffer_out;
size_t buffer_in_size, buffer_out_size, num_read, remaining;
int rc;
#if ZSTD_VERSION_NUMBER >= 10400 /* zstd ≥ 1.4.0 */
ZSTD_CCtx *cctx = NULL;
ZSTD_EndDirective mode;
int finished, last_chunk;
#else /* zstd < 1.4.0 */
ZSTD_CStream *cstream = NULL;
size_t result, to_read;
#endif
ZSTD_inBuffer input;
ZSTD_outBuffer output;
source = NULL;
dest = NULL;
buffer_in = NULL;
buffer_out = NULL;
rc = 0;
if (!from || !to || (compression_level < 1) || (compression_level > 19))
goto end;
buffer_in_size = ZSTD_CStreamInSize ();
buffer_in = malloc (buffer_in_size);
if (!buffer_in)
goto end;
buffer_out_size = ZSTD_CStreamOutSize ();
buffer_out = malloc (buffer_out_size);
if (!buffer_out)
goto end;
source = fopen (from, "rb");
if (!source)
goto end;
dest = fopen (to, "wbx");
if (!dest)
goto end;
#if ZSTD_VERSION_NUMBER >= 10400 /* zstd ≥ 1.4.0 */
cctx = ZSTD_createCCtx ();
if (!cctx)
goto error;
ZSTD_CCtx_setParameter (cctx, ZSTD_c_compressionLevel, compression_level);
while (1)
{
num_read = fread (buffer_in, 1, buffer_in_size, source);
if (ferror (source))
goto error;
last_chunk = (num_read < buffer_in_size);
mode = (last_chunk) ? ZSTD_e_end : ZSTD_e_continue;
input.src = buffer_in;
input.size = num_read;
input.pos = 0;
finished = 0;
while (!finished)
{
output.dst = buffer_out;
output.size = buffer_out_size;
output.pos = 0;
remaining = ZSTD_compressStream2(cctx, &output , &input, mode);
if (ZSTD_isError (remaining))
goto error;
if ((fwrite (buffer_out, 1, output.pos, dest) != output.pos)
|| ferror (dest))
{
goto error;
}
finished = (last_chunk) ? (remaining == 0) : (input.pos == input.size);
};
if (input.pos != input.size)
goto error;
if (last_chunk)
break;
}
#else /* zstd < 1.4.0 */
cstream = ZSTD_createCStream ();
if (!cstream)
goto error;
result = ZSTD_initCStream (cstream, compression_level);
if (ZSTD_isError (result))
goto error;
to_read = buffer_in_size;
while ((num_read = fread (buffer_in, 1, buffer_in_size, source)))
{
input.src = buffer_in;
input.size = num_read;
input.pos = 0;
while (input.pos < input.size)
{
output.dst = buffer_out;
output.size = buffer_out_size;
output.pos = 0;
to_read = ZSTD_compressStream (cstream, &output , &input);
if (ZSTD_isError (to_read))
goto error;
if (to_read > buffer_in_size)
to_read = buffer_in_size;
if ((fwrite (buffer_out, 1, output.pos, dest) != output.pos)
|| ferror (dest))
{
goto error;
}
}
}
output.dst = buffer_out;
output.size = buffer_out_size;
output.pos = 0;
remaining = ZSTD_endStream (cstream, &output);
if (remaining)
goto error;
if ((fwrite (buffer_out, 1, output.pos, dest) != output.pos)
|| ferror (dest))
{
goto error;
}
#endif
rc = 1;
goto end;
error:
#if ZSTD_VERSION_NUMBER >= 10400 /* zstd ≥ 1.4.0 */
if (cctx)
{
ZSTD_freeCCtx (cctx);
cctx = NULL;
}
#else /* zstd < 1.4.0 */
if (cstream)
{
ZSTD_freeCStream (cstream);
cstream = NULL;
}
#endif
unlink (to);
end:
#if ZSTD_VERSION_NUMBER >= 10400 /* zstd ≥ 1.4.0 */
if (cctx)
ZSTD_freeCCtx (cctx);
#else /* zstd < 1.4.0 */
if (cstream)
ZSTD_freeCStream (cstream);
#endif
free (buffer_in);
free (buffer_out);
if (source)
fclose (source);
if (dest)
fclose (dest);
return rc;
#else
/* make C compiler happy */
(void) from;
(void) to;
(void) compression_level;
return 0;
#endif /* HAVE_ZSTD */
}
/*
* Compresses a file with gzip or zstandard.
*
* The output file must not exist.
*
* Supported values for parameter "compressor":
* - "gzip": gzip compression (via zlib)
* - "zstd": zstandard compression (it must be enabled at build time)
*
* Parameter "compression_level" is the compression level as percentage:
* from 1 (fast, low compression) to 100 (slow, best compression).
*
* Returns:
* 1: OK
* 0: error
*/
int
dir_file_compress (const char *filename_input,
const char *filename_output,
const char *compressor,
int compression_level)
{
int level;
if (!compressor || (compression_level < 1) || (compression_level > 100))
return 0;
if (strcmp (compressor, "gzip") == 0)
{
/* convert percent to zlib compression level (1-9) */
level = (((compression_level - 1) * 9) / 100) + 1;
return dir_file_compress_gzip (filename_input, filename_output, level);
}
else if (strcmp (compressor, "zstd") == 0)
{
/* convert percent to zstd compression level (1-19) */
level = (((compression_level - 1) * 19) / 100) + 1;
return dir_file_compress_zstd (filename_input, filename_output, level);
}
else
{
return 0;
}
}