Bug Summary

File:.build-ci/../shared/fs-util.c
Warning:line 176, column 7
Potential leak of memory pointed to by 'file_path'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name fs-util.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -fcoverage-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -resource-dir /usr/bin/../lib/clang/22 -include /__w/nvme-cli/nvme-cli/.build-ci/nvme-config.h -I shared/libshared.a.p -I shared -I ../shared -I ccan -I ../ccan -D _FILE_OFFSET_BITS=64 -D _GNU_SOURCE -U NDEBUG -internal-isystem /usr/bin/../lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/16/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-opt-analyze-headers -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /__w/nvme-cli/nvme-cli/.build-ci/scan-results/2026-10-24-125112-639-1 -x c ../shared/fs-util.c
1// SPDX-License-Identifier: LGPL-2.1-or-later
2/*
3 * This file is part of nvme-cli.
4 * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
5 *
6 * Authors: Martin Belanger <martin.belanger@dell.com>
7 */
8
9#include <dirent.h>
10#include <errno(*__errno_location ()).h>
11#include <limits.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/stat.h>
16#include <unistd.h>
17
18#include "cleanup-util.h"
19#include "fs-util.h"
20
21int shr_mkdir_p(const char *path, mode_t mode)
22{
23 char buf[PATH_MAX4096];
24 char *p;
25 size_t len;
26 int ret;
27
28 len = strlen(path);
29 if (len >= sizeof(buf))
30 return -ENAMETOOLONG36;
31 memcpy(buf, path, len + 1);
32 if (len && buf[len - 1] == '/')
33 buf[len - 1] = '\0';
34
35 for (p = buf + 1; *p; p++) {
36 if (*p != '/')
37 continue;
38 *p = '\0';
39 ret = shr_mkdir(buf, mode);
40 *p = '/';
41 if (ret < 0 && ret != -EEXIST17)
42 return ret;
43 }
44 ret = shr_mkdir(buf, mode);
45 return (ret == 0 || ret == -EEXIST17) ? 0 : ret;
46}
47
48int shr_mkdir_from_fname(const char *file, mode_t mode)
49{
50 __cleanup_free__attribute__((cleanup(shr_freep))) char *file_copy = NULL((void*)0);
51 char *parent;
52 size_t len = strlen(file);
53
54 /*
55 * A trailing '/' means there is no file name to strip and the path is
56 * a directory in full. dirname() cannot be used for it: it removes
57 * the trailing '/' before the last component, so "a/b/" would come
58 * back as "a" and leave "a/b" uncreated.
59 */
60 if (len && file[len - 1] == '/')
61 return shr_mkdir_p(file, mode);
62
63 file_copy = strdup(file);
64 if (!file_copy)
65 return -ENOMEM12;
66
67 parent = shr_dirname(file_copy);
68 return shr_mkdir_p(parent, mode);
69}
70
71char *shr_basename(const char *path)
72{
73 char *p = (char *)strrchr(path, '/')_Generic (0 ? (path) : (void *) 1, const void *: (const char *
) (strrchr (path, '/')), default: strrchr (path, '/'))
;
74
75 return p ? p + 1 : (char *)path;
76}
77
78size_t shr_dir_prefix_len(const char *path)
79{
80 return (size_t)(shr_basename(path) - path);
81}
82
83static char *join_path(const char *dir, const char *path)
84{
85 char *out;
86 size_t len;
87
88 if (!dir)
3
Assuming 'dir' is null
4
Taking true branch
89 return strdup(path);
5
Memory is allocated
90 if (!path || !*path)
91 return strdup(dir);
92
93 len = strlen(dir) + 1 + strlen(path) + 1;
94 out = malloc(len);
95 if (!out)
96 return NULL((void*)0);
97 snprintf(out, len, "%s/%s", dir, path);
98
99 return out;
100}
101
102int shr_rmdir_recursive(const char *path)
103{
104 DIR *dir;
105 struct dirent *entry;
106 char child[PATH_MAX4096];
107 int ret;
108
109 /*
110 * opendir() follows symlinks, so without this check a path that is
111 * itself a symlink to a directory would have its target's contents
112 * walked and removed instead of just the link, like "rm -rf" does.
113 * A missing path is not a directory either, so unlink() below runs
114 * for it too and reports the ENOENT that makes this a no-op.
115 */
116 if (!shr_isdir(path))
117 return unlink(path) == 0 || errno(*__errno_location ()) == ENOENT2 ? 0 : -errno(*__errno_location ());
118
119 dir = opendir(path);
120 if (!dir)
121 return errno(*__errno_location ()) == ENOENT2 ? 0 : -errno(*__errno_location ());
122
123 while ((entry = readdir(dir))) {
124 int saved_errno;
125
126 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
127 continue;
128
129 if (snprintf(child, sizeof(child), "%s/%s",
130 path, entry->d_name) >= (int)sizeof(child)) {
131 closedir(dir);
132 return -ENAMETOOLONG36;
133 }
134
135 if (unlink(child) == 0 || errno(*__errno_location ()) == ENOENT2)
136 continue;
137
138 /*
139 * Unlinking a directory fails with EISDIR or EPERM on Linux,
140 * EACCES on Windows. Any other errno means child could not be
141 * removed at all, directory or not.
142 */
143 if (errno(*__errno_location ()) != EISDIR21 && errno(*__errno_location ()) != EPERM1 && errno(*__errno_location ()) != EACCES13) {
144 saved_errno = errno(*__errno_location ());
145 closedir(dir);
146 return -saved_errno;
147 }
148
149 ret = shr_rmdir_recursive(child);
150 if (ret < 0 && ret != -ENOENT2) {
151 closedir(dir);
152 return ret;
153 }
154 }
155
156 closedir(dir);
157
158 ret = shr_rmdir(path);
159 return (ret == 0 || ret == -ENOENT2) ? 0 : ret;
160}
161
162int shr_read_file(const char *dir, const char *path, long *size,
163 unsigned char **out)
164{
165 __cleanup_free__attribute__((cleanup(shr_freep))) char *file_path = NULL((void*)0);
166 unsigned char *buf;
167 FILE *file;
168 long file_size;
169 size_t n;
170 int ret;
171
172 file_path = join_path(dir, path);
2
Calling 'join_path'
6
Returned allocated memory
173 if (!file_path)
7
Assuming 'file_path' is non-null
8
Taking false branch
174 return -ENOMEM12;
175
176 file = fopen(file_path, "rb");
9
Potential leak of memory pointed to by 'file_path'
177 if (!file)
178 return -errno(*__errno_location ());
179
180 if (fseek(file, 0, SEEK_END2) != 0 || (file_size = ftell(file)) < 0) {
181 ret = -errno(*__errno_location ());
182 goto close_file;
183 }
184 if (file_size == 0) {
185 ret = -ENODATA61;
186 goto close_file;
187 }
188 if (fseek(file, 0, SEEK_SET0) != 0) {
189 ret = -errno(*__errno_location ());
190 goto close_file;
191 }
192
193 buf = malloc(file_size);
194 if (!buf) {
195 ret = -ENOMEM12;
196 goto close_file;
197 }
198
199 n = fread(buf, 1, file_size, file);
200 if (n != (size_t)file_size) {
201 free(buf);
202 ret = -EIO5;
203 goto close_file;
204 }
205
206 *size = file_size;
207 *out = buf;
208 ret = 0;
209
210close_file:
211 fclose(file);
212 return ret;
213}
214
215int shr_read_file_as_string(const char *dir, const char *path, long *size,
216 char **out)
217{
218 unsigned char *raw = NULL((void*)0);
219 char *str;
220 long raw_size = 0;
221 int ret;
222
223 ret = shr_read_file(dir, path, &raw_size, &raw);
1
Calling 'shr_read_file'
224 if (ret < 0 && ret != -ENODATA61)
225 return ret;
226
227 str = realloc(raw, raw_size + 1);
228 if (!str) {
229 free(raw);
230 return -ENOMEM12;
231 }
232 str[raw_size] = '\0';
233
234 if (size)
235 *size = raw_size;
236
237 *out = str;
238 return 0;
239}