fix(get_command_path): leak when path unset and calling file present in current dir

$ touch cat
$ unset PATH
$ cat
m==75845== Memcheck, a memory error detector
==75845== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==75845== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==75845== Command: ./minishell
==75845==
==75846== Memcheck, a memory error detector
==75846== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==75846== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==75846== Command: /nix/store/6wgd8c9vq93mqxzc7jhkl86mv6qbc360-coreutils-9.5/bin/touch cat
==75846==
==75846==
==75846== FILE DESCRIPTORS: 0 open (0 std) at exit.
==75846==
==75846== HEAP SUMMARY:
==75846==     in use at exit: 0 bytes in 0 blocks
==75846==   total heap usage: 46 allocs, 46 frees, 8,126 bytes allocated
==75846==
==75846== All heap blocks were freed -- no leaks are possible
==75846==
==75846== For lists of detected and suppressed errors, rerun with: -s
==75846== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
minishell: cat: Permission denied
==75849==
==75849== FILE DESCRIPTORS: 3 open (3 std) at exit.
==75849==
==75849== HEAP SUMMARY:
==75849==     in use at exit: 4,181 bytes in 12 blocks
==75849==   total heap usage: 982 allocs, 970 frees, 122,299 bytes allocated
==75849==
==75849== 8 bytes in 1 blocks are definitely lost in loss record 1 of 9
==75849==    at 0x48467D9: malloc (in /nix/store/phbnjdfmy3v4ak9xf211y2336mv5kx9s-valgrind-3.23.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==75849==    by 0x113C22: ft_calloc (ft_calloc.c:28)
==75849==    by 0x112399: get_paths_array (src/subst/simple_filename_exp.c:37)
==75849==    by 0x1121EE: filepath_from_env (src/subst/simple_filename_exp.c:144)
==75849==    by 0x1121C0: get_cmdpath (src/subst/simple_filename_exp.c:184)
==75849==    by 0x10CD97: exec_external_cmd (src/executing/simple_cmd/simple_cmd_execute.c:67)
==75849==    by 0x10CC58: simple_cmd_execute (src/executing/simple_cmd/simple_cmd_execute.c:94)
==75849==    by 0x10B12C: cmd_execute (src/executing/cmd/cmd_execute.c:23)
==75849==    by 0x10A4FD: execute_command (src/minishell.c:37)
==75849==    by 0x10A3B2: main (src/minishell.c:90)
==75849==
==75849== 24 bytes in 1 blocks are still reachable in loss record 6 of 9
==75849==    at 0x48467D9: malloc (in /nix/store/phbnjdfmy3v4ak9xf211y2336mv5kx9s-valgrind-3.23.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==75849==    by 0x113C22: ft_calloc (ft_calloc.c:28)
==75849==    by 0x10D4A9: cmd_create (src/parser/cmd/cmd.c:20)
==75849==    by 0x10F6A1: simple_cmd_create (src/parser/simple_cmd/simple_cmd_parse.c:24)
==75849==    by 0x10F4E4: minishell_simple_cmd_parse (src/parser/simple_cmd/simple_cmd_parse.c:38)
==75849==    by 0x10E078: minishell_group_or_simple_parse (src/parser/group_cmd/group_cmd_parse.c:53)
==75849==    by 0x10E4AC: minishell_pipeline_parse (src/parser/pipeline/pipeline_parse.c:26)
==75849==    by 0x10D93C: minishell_cmds_parse (src/parser/cmd/cmds_parse.c:30)
==75849==    by 0x1139D5: minishell_parse (src/parser/cmd_parsing.c:67)
==75849==    by 0x10A38B: main (src/minishell.c:87)
==75849==
==75849== LEAK SUMMARY:
==75849==    definitely lost: 8 bytes in 1 blocks
==75849==    indirectly lost: 0 bytes in 0 blocks
==75849==      possibly lost: 0 bytes in 0 blocks
==75849==    still reachable: 24 bytes in 1 blocks
==75849==         suppressed: 4,149 bytes in 10 blocks
==75849==
==75849== For lists of detected and suppressed errors, rerun with: -s
==75849== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
126
==75845==
==75845== FILE DESCRIPTORS: 3 open (3 std) at exit.
==75845==
==75845== HEAP SUMMARY:
==75845==     in use at exit: 4,201 bytes in 14 blocks
==75845==   total heap usage: 844 allocs, 830 frees, 72,248 bytes allocated
==75845==
==75845== 8 bytes in 1 blocks are definitely lost in loss record 1 of 8
==75845==    at 0x48467D9: malloc (in /nix/store/phbnjdfmy3v4ak9xf211y2336mv5kx9s-valgrind-3.23.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==75845==    by 0x113C22: ft_calloc (ft_calloc.c:28)
==75845==    by 0x112399: get_paths_array (src/subst/simple_filename_exp.c:37)
==75845==    by 0x1121EE: filepath_from_env (src/subst/simple_filename_exp.c:144)
==75845==    by 0x1121C0: get_cmdpath (src/subst/simple_filename_exp.c:184)
==75845==    by 0x10CD97: exec_external_cmd (src/executing/simple_cmd/simple_cmd_execute.c:67)
==75845==    by 0x10CC58: simple_cmd_execute (src/executing/simple_cmd/simple_cmd_execute.c:94)
==75845==    by 0x10B12C: cmd_execute (src/executing/cmd/cmd_execute.c:23)
==75845==    by 0x10A4FD: execute_command (src/minishell.c:37)
==75845==    by 0x10A3B2: main (src/minishell.c:90)
==75845==
==75845== LEAK SUMMARY:
==75845==    definitely lost: 8 bytes in 1 blocks
==75845==    indirectly lost: 0 bytes in 0 blocks
==75845==      possibly lost: 0 bytes in 0 blocks
==75845==    still reachable: 0 bytes in 0 blocks
==75845==         suppressed: 4,193 bytes in 13 blocks
==75845==
==75845== For lists of detected and suppressed errors, rerun with: -s
==75845== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)inishell: cat: Permission denied
This commit is contained in:
Khaïs COLIN 2025-04-24 17:46:54 +02:00
parent a86616f910
commit 9d37d07589
4 changed files with 189 additions and 147 deletions

View file

@ -6,163 +6,18 @@
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/03/02 13:40:10 by jguelen #+# #+# */
/* Updated: 2025/04/01 18:38:35 by khais ### ########.fr */
/* Updated: 2025/04/24 17:58:39 by khais ### ########.fr */
/* */
/* ************************************************************************** */
#include "../minishell.h"
#include "libft.h"
#include "../ft_errno.h"
#include "../env/env_manip.h"
#include "path_split.h"
#include "simple_filename_exp_utils_utils.h"
#include "subst.h"
#include <stdbool.h>
#include <sys/stat.h>
/*
** The return value of this function is always safe to destroy with
** path_split_destroy.
** Returns a NULL-terminated array containing each possible path contained
** in the PATH environnement variable.
** Returns NULL in case of an allocation error.
*/
static char **get_paths_array(t_env *env)
{
char **path_array;
t_env *path_node;
path_node = env_find_node_bykey(env, "PATH");
if (path_node == NULL || !path_node->value || !path_node->value[0])
{
path_array = ft_calloc(1, sizeof(char *));
if (!path_array)
return (NULL);
return (path_array);
}
path_array = path_split(path_node->value, ':');
if (!path_array)
return (NULL);
return (path_array);
}
/*
** Utility function that attempts to find a regular executable file for which
** the execution rights are granted. It disregards non-regular files and so in
** particular directories (note that using stat allows us to follow symlinks to
** their target if encountered). If a file is found here for which the execution
** permission is not granted and no elligible file was found before its path is
** stored inside oldpath so that in case no file is found to be executable it
** is the value stored in oldpath that will be used resulting in a Permission
** denied error. oldpath is to store the first occurrence of a regular file
** corresponding to the filepath but not executable.
**
** We disregard all stat(2) failures, since bash does the same and treats them
** all to mean that the file does not exist.
*/
static char *select_path(char *filepath, char **oldpath, char **path,
struct stat *fstat)
{
int ret;
ret = stat(filepath, fstat);
if (ret == -1)
return (NULL);
if (access(filepath, F_OK) == 0 && ret == 0 && S_ISREG(fstat->st_mode))
{
if (access(filepath, X_OK) != 0)
{
if (!(*oldpath))
*oldpath = filepath;
}
else
return (free(*oldpath), path_split_destroy(path), filepath);
}
return (NULL);
}
/*
** This function exists to implement the bash behaviour specific to where
** the PATH variable has been set with a non-empty value.
** It checks the working directory if there is a void inside PATH and tries to
** find the executable command inside the path entry itself otherwise
** disregarding directories and selecting the first entry that both exists and
** is executable. If no such entry exists but one or more regular file(s)
** exist(s) with the correct name in one of the entries (it therefore lacks the
** execution permission) this function will return the first found.
** Returns NULL if an error occurred or nothing corresponds to filename
** in PATH. ft_errno is set in the course of this function to help distinguish
** the nature of the case.
*/
static char *deal_with_filled_path(char *filename, char **path,
struct stat *fstat)
{
size_t i;
char *oldpath;
char *filepath;
char *tmp;
i = 0;
oldpath = NULL;
while (path[i])
{
if (!path[i][0])
filepath = alloc_path(".", filename);
else
filepath = alloc_path(path[i], filename);
if (!filepath)
return (ft_errno(FT_ENOMEM), NULL);
tmp = select_path(filepath, &oldpath, path, fstat);
if (tmp)
return (filepath);
free(filepath);
i++;
}
path_split_destroy(path);
return (oldpath);
}
/*
** This function searches the environment to resolve a full path for an
** executable file corresponding to filename. It deals with two types of bash
** behaviours: a PATH unset or corresponding to an empty value and a PATH set
** and with a non-empty value.
** In the first case it searches the current working directory for the file
** coorresponding to filename. If no regular file is found it returns NULL and
** distinguishes the case where a directory was found corresponding to filename
** by setting ft_errno to FT_ISDIR.
** The second case is dealt with using deal_with_filled_path (see the function
** itself for details).
** Returns NULL on error or if nothing is found.
*/
static char *filepath_from_env(char *filename, t_minishell *app)
{
char *filepath;
char **path;
struct stat fstat;
int ret;
path = get_paths_array(app->env);
if (path == NULL)
return (ft_errno(FT_ENOMEM), NULL);
if (!path[0])
{
filepath = alloc_path(".", filename);
if (!filepath)
return (ft_errno(FT_ENOMEM), NULL);
ret = stat(filepath, &fstat);
if (ret == -1)
return (ft_errno(FT_STATFAIL), free(filepath), NULL);
if (access(filepath, F_OK) == 0)
{
if (S_ISDIR(fstat.st_mode))
return (free(filepath), ft_errno(FT_ISDIR), NULL);
else if (S_ISREG(fstat.st_mode))
return (filepath);
}
}
return (deal_with_filled_path(filename, path, &fstat));
}
/*
** Returns a malloc-allocated string representing the full path to
** the command or executable corresponding to name or NULL in case of

View file

@ -0,0 +1,166 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* simple_filename_exp_utils_utils.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/04/24 17:56:47 by khais #+# #+# */
/* Updated: 2025/04/24 18:00:33 by khais ### ########.fr */
/* */
/* ************************************************************************** */
#include "../ft_errno.h"
#include "../env/env_manip.h"
#include "simple_filename_exp_utils_utils.h"
#include "subst.h"
#include "path_split.h"
/*
** The return value of this function is always safe to destroy with
** path_split_destroy.
** Returns a NULL-terminated array containing each possible path contained
** in the PATH environnement variable.
** Returns NULL in case of an allocation error.
*/
static char **get_paths_array(t_env *env)
{
char **path_array;
t_env *path_node;
path_node = env_find_node_bykey(env, "PATH");
if (path_node == NULL || !path_node->value || !path_node->value[0])
{
path_array = ft_calloc(1, sizeof(char *));
if (!path_array)
return (NULL);
return (path_array);
}
path_array = path_split(path_node->value, ':');
if (!path_array)
return (NULL);
return (path_array);
}
/*
** Utility function that attempts to find a regular executable file for which
** the execution rights are granted. It disregards non-regular files and so in
** particular directories (note that using stat allows us to follow symlinks to
** their target if encountered). If a file is found here for which the execution
** permission is not granted and no elligible file was found before its path is
** stored inside oldpath so that in case no file is found to be executable it
** is the value stored in oldpath that will be used resulting in a Permission
** denied error. oldpath is to store the first occurrence of a regular file
** corresponding to the filepath but not executable.
**
** We disregard all stat(2) failures, since bash does the same and treats them
** all to mean that the file does not exist.
*/
static char *select_path(char *filepath, char **oldpath, char **path,
struct stat *fstat)
{
int ret;
ret = stat(filepath, fstat);
if (ret == -1)
return (NULL);
if (access(filepath, F_OK) == 0 && ret == 0 && S_ISREG(fstat->st_mode))
{
if (access(filepath, X_OK) != 0)
{
if (!(*oldpath))
*oldpath = filepath;
}
else
return (free(*oldpath), path_split_destroy(path), filepath);
}
return (NULL);
}
/*
** This function exists to implement the bash behaviour specific to where
** the PATH variable has been set with a non-empty value.
** It checks the working directory if there is a void inside PATH and tries to
** find the executable command inside the path entry itself otherwise
** disregarding directories and selecting the first entry that both exists and
** is executable. If no such entry exists but one or more regular file(s)
** exist(s) with the correct name in one of the entries (it therefore lacks the
** execution permission) this function will return the first found.
** Returns NULL if an error occurred or nothing corresponds to filename
** in PATH. ft_errno is set in the course of this function to help distinguish
** the nature of the case.
*/
static char *deal_with_filled_path(char *filename, char **path,
struct stat *fstat)
{
size_t i;
char *oldpath;
char *filepath;
char *tmp;
i = 0;
oldpath = NULL;
while (path[i])
{
if (!path[i][0])
filepath = alloc_path(".", filename);
else
filepath = alloc_path(path[i], filename);
if (!filepath)
return (ft_errno(FT_ENOMEM), NULL);
tmp = select_path(filepath, &oldpath, path, fstat);
if (tmp)
return (filepath);
free(filepath);
i++;
}
path_split_destroy(path);
return (oldpath);
}
static char *filter_dir(struct stat fstat, char *filepath)
{
if (S_ISDIR(fstat.st_mode))
return (free(filepath), ft_errno(FT_ISDIR), NULL);
else if (S_ISREG(fstat.st_mode))
return (filepath);
return (free(filepath), NULL);
}
/*
** This function searches the environment to resolve a full path for an
** executable file corresponding to filename. It deals with two types of bash
** behaviours: a PATH unset or corresponding to an empty value and a PATH set
** and with a non-empty value.
** In the first case it searches the current working directory for the file
** coorresponding to filename. If no regular file is found it returns NULL and
** distinguishes the case where a directory was found corresponding to filename
** by setting ft_errno to FT_ISDIR.
** The second case is dealt with using deal_with_filled_path (see the function
** itself for details).
** Returns NULL on error or if nothing is found.
*/
char *filepath_from_env(char *filename, t_minishell *app)
{
char *filepath;
char **path;
struct stat fstat;
int ret;
path = get_paths_array(app->env);
if (path == NULL)
return (ft_errno(FT_ENOMEM), NULL);
if (!path[0])
{
path_split_destroy(path);
filepath = alloc_path(".", filename);
if (!filepath)
return (ft_errno(FT_ENOMEM), NULL);
ret = stat(filepath, &fstat);
if (ret == -1)
return (ft_errno(FT_STATFAIL), free(filepath), NULL);
if (access(filepath, F_OK) == 0)
return (filter_dir(fstat, filepath));
}
return (deal_with_filled_path(filename, path, &fstat));
}

View file

@ -0,0 +1,20 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* simple_filename_exp_utils_utils.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/04/24 17:57:22 by khais #+# #+# */
/* Updated: 2025/04/24 18:00:56 by khais ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef SIMPLE_FILENAME_EXP_UTILS_UTILS_H
# define SIMPLE_FILENAME_EXP_UTILS_UTILS_H
# include "../minishell.h"
char *filepath_from_env(char *filename, t_minishell *app);
#endif // SIMPLE_FILENAME_EXP_UTILS_UTILS_H