Bug Summary

File:utils/file.c
Warning:line 365, column 8
The 1st argument to 'unlinkat' is between -99 and -1 but should be a valid file descriptor or AT_FDCWD

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name file.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 -pic-is-pie -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=/var/lib/jenkins/workspace/scan-build-netsurf -fcoverage-compilation-dir=/var/lib/jenkins/workspace/scan-build-netsurf -resource-dir /usr/lib/llvm-19/lib/clang/19 -isystem /usr/include/mit-krb5 -I . -I include -I build/Linux-framebuffer -I frontends -I content/handlers -D WITH_JPEG -U WITH_PDF_EXPORT -D LIBICONV_PLUG -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -I /usr/include/x86_64-linux-gnu -I /usr/include/p11-kit-1 -D WITH_CURL -D WITH_OPENSSL -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D UTF8PROC_EXPORTS -D WITH_UTF8PROC -I /usr/include/webp -D WITH_WEBP -I /usr/include/libpng16 -D WITH_PNG -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include/ -D WITH_BMP -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D WITH_GIF -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D WITH_NS_SVG -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D WITH_NSSPRITE -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D WITH_NSPSL -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D WITH_NSLOG -D NETSURF_UA_FORMAT_STRING="Mozilla/5.0 (%s) NetSurf/%d.%d" -D NETSURF_HOMEPAGE="about:welcome" -D NETSURF_LOG_LEVEL=VERBOSE -D NETSURF_BUILTIN_LOG_FILTER="(level:WARNING || cat:jserrors)" -D NETSURF_BUILTIN_VERBOSE_FILTER="(level:VERBOSE || cat:jserrors)" -D STMTEXPR=1 -D nsframebuffer -D small -D NETSURF_FB_RESPATH="${HOME}/.netsurf/:${NETSURFRES}:/var/lib/jenkins/artifacts-x86_64-linux-gnu/share/netsurf:./frontends/framebuffer/res" -D NETSURF_FB_FONTPATH="/usr/share/fonts/truetype/dejavu:/usr/share/fonts/truetype/msttcorefonts" -D NETSURF_FB_FONT_SANS_SERIF="DejaVuSans.ttf" -D NETSURF_FB_FONT_SANS_SERIF_BOLD="DejaVuSans-Bold.ttf" -D NETSURF_FB_FONT_SANS_SERIF_ITALIC="DejaVuSans-Oblique.ttf" -D NETSURF_FB_FONT_SANS_SERIF_ITALIC_BOLD="DejaVuSans-BoldOblique.ttf" -D NETSURF_FB_FONT_SERIF="DejaVuSerif.ttf" -D NETSURF_FB_FONT_SERIF_BOLD="DejaVuSerif-Bold.ttf" -D NETSURF_FB_FONT_MONOSPACE="DejaVuSansMono.ttf" -D NETSURF_FB_FONT_MONOSPACE_BOLD="DejaVuSansMono-Bold.ttf" -D NETSURF_FB_FONT_CURSIVE="Comic_Sans_MS.ttf" -D NETSURF_FB_FONT_FANTASY="Impact.ttf" -I /var/lib/jenkins/artifacts-x86_64-linux-gnu/include -D _POSIX_C_SOURCE=200809L -D _XOPEN_SOURCE=700 -D _BSD_SOURCE -D _DEFAULT_SOURCE -D _NETBSD_SOURCE -D DUK_OPT_HAVE_CUSTOM_H -internal-isystem /usr/lib/llvm-19/lib/clang/19/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -Wwrite-strings -Wno-unused-parameter -Wno-unused-but-set-variable -std=c99 -fconst-strings -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -vectorize-loops -vectorize-slp -analyzer-display-progress -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /var/lib/jenkins/workspace/scan-build-netsurf/clangScanBuildReports/2025-11-24-121917-2152729-1 -x c utils/file.c
1/*
2 * Copyright 2014 vincent Sanders <vince@netsurf-browser.org>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/** \file
20 * Table operations for files with posix compliant path separator.
21 */
22
23#include <stdarg.h>
24#include <string.h>
25#include <stdio.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <unistd.h>
29#include <fcntl.h>
30#include <errno(*__errno_location ()).h>
31
32#include "desktop/gui_internal.h"
33
34#include "utils/utils.h"
35#include "utils/corestrings.h"
36#include "utils/url.h"
37#include "utils/nsurl.h"
38#include "utils/string.h"
39#include "utils/file.h"
40#include "utils/dirent.h"
41
42#ifdef nsamiga
43#include "frontends/amiga/os3support.h"
44#endif
45
46/**
47 * Generate a posix path from one or more component elemnts.
48 *
49 * If a string is allocated it must be freed by the caller.
50 *
51 * @param[in,out] str pointer to string pointer if this is NULL enough
52 * storage will be allocated for the complete path.
53 * @param[in,out] size The size of the space available if \a str not
54 * NULL on input and if not NULL set to the total
55 * output length on output.
56 * @param[in] nelm The number of elements.
57 * @param[in] ap The elements of the path as string pointers.
58 * @return NSERROR_OK and the complete path is written to str
59 * or error code on faliure.
60 */
61static nserror posix_vmkpath(char **str, size_t *size, size_t nelm, va_list ap)
62{
63 return vsnstrjoin(str, size, '/', nelm, ap);
64}
65
66/**
67 * Get the basename of a file using posix path handling.
68 *
69 * This gets the last element of a path and returns it.
70 *
71 * @param[in] path The path to extract the name from.
72 * @param[in,out] str Pointer to string pointer if this is NULL enough
73 * storage will be allocated for the path element.
74 * @param[in,out] size The size of the space available if \a
75 * str not NULL on input and set to the total
76 * output length on output.
77 * @return NSERROR_OK and the complete path is written to str
78 * or error code on faliure.
79 */
80static nserror posix_basename(const char *path, char **str, size_t *size)
81{
82 const char *leafname;
83 char *fname;
84
85 if (path == NULL((void*)0)) {
86 return NSERROR_BAD_PARAMETER;
87 }
88
89 leafname = strrchr(path, '/');
90 if (!leafname) {
91 leafname = path;
92 } else {
93 leafname += 1;
94 }
95
96 fname = strdup(leafname);
97 if (fname == NULL((void*)0)) {
98 return NSERROR_NOMEM;
99 }
100
101 *str = fname;
102 if (size != NULL((void*)0)) {
103 *size = strlen(fname);
104 }
105 return NSERROR_OK;
106}
107
108/**
109 * Create a path from a nsurl using posix file handling.
110 *
111 * @param[in] url The url to encode.
112 * @param[out] path_out A string containing the result path which should
113 * be freed by the caller.
114 * @return NSERROR_OK and the path is written to \a path or error code
115 * on faliure.
116 */
117static nserror posix_nsurl_to_path(struct nsurl *url, char **path_out)
118{
119 lwc_string *urlpath;
120 char *path;
121 bool_Bool match;
122 lwc_string *scheme;
123 nserror res;
124
125 if ((url == NULL((void*)0)) || (path_out == NULL((void*)0))) {
126 return NSERROR_BAD_PARAMETER;
127 }
128
129 scheme = nsurl_get_component(url, NSURL_SCHEME);
130
131 if (lwc_string_caseless_isequal(scheme, corestring_lwc_file,({ lwc_error __lwc_err = lwc_error_ok; lwc_string *__lwc_str1
= (scheme); lwc_string *__lwc_str2 = (corestring_lwc_file); _Bool
*__lwc_ret = (&match); if (__lwc_str1->insensitive ==
((void*)0)) { __lwc_err = lwc__intern_caseless_string(__lwc_str1
); } if (__lwc_err == lwc_error_ok && __lwc_str2->
insensitive == ((void*)0)) { __lwc_err = lwc__intern_caseless_string
(__lwc_str2); } if (__lwc_err == lwc_error_ok) *__lwc_ret = (
__lwc_str1->insensitive == __lwc_str2->insensitive); __lwc_err
; })
132 &match)({ lwc_error __lwc_err = lwc_error_ok; lwc_string *__lwc_str1
= (scheme); lwc_string *__lwc_str2 = (corestring_lwc_file); _Bool
*__lwc_ret = (&match); if (__lwc_str1->insensitive ==
((void*)0)) { __lwc_err = lwc__intern_caseless_string(__lwc_str1
); } if (__lwc_err == lwc_error_ok && __lwc_str2->
insensitive == ((void*)0)) { __lwc_err = lwc__intern_caseless_string
(__lwc_str2); } if (__lwc_err == lwc_error_ok) *__lwc_ret = (
__lwc_str1->insensitive == __lwc_str2->insensitive); __lwc_err
; })
!= lwc_error_ok)
133 {
134 return NSERROR_BAD_PARAMETER;
135 }
136 lwc_string_unref(scheme){ lwc_string *__lwc_s = (scheme); ((__lwc_s != ((void*)0)) ? (
void) (0) : __assert_fail ("__lwc_s != NULL", "utils/file.c",
136, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt
--; if ((__lwc_s->refcnt == 0) || ((__lwc_s->refcnt == 1
) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy
(__lwc_s); }
;
137 if (match == false0) {
138 return NSERROR_BAD_PARAMETER;
139 }
140
141 urlpath = nsurl_get_component(url, NSURL_PATH);
142 if (urlpath == NULL((void*)0)) {
143 return NSERROR_BAD_PARAMETER;
144 }
145
146 res = url_unescape(lwc_string_data(urlpath)({((urlpath != ((void*)0)) ? (void) (0) : __assert_fail ("urlpath != NULL"
, "utils/file.c", 146, __extension__ __PRETTY_FUNCTION__)); (
const char *)((urlpath)+1);})
,
147 lwc_string_length(urlpath)({((urlpath != ((void*)0)) ? (void) (0) : __assert_fail ("urlpath != NULL"
, "utils/file.c", 147, __extension__ __PRETTY_FUNCTION__)); (
urlpath)->len;})
,
148 NULL((void*)0),
149 &path);
150 lwc_string_unref(urlpath){ lwc_string *__lwc_s = (urlpath); ((__lwc_s != ((void*)0)) ?
(void) (0) : __assert_fail ("__lwc_s != NULL", "utils/file.c"
, 150, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt
--; if ((__lwc_s->refcnt == 0) || ((__lwc_s->refcnt == 1
) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy
(__lwc_s); }
;
151 if (res != NSERROR_OK) {
152 return res;
153 }
154
155 *path_out = path;
156
157 return NSERROR_OK;
158}
159
160/**
161 * Create a nsurl from a path using posix file handling.
162 *
163 * Perform the necessary operations on a path to generate a nsurl.
164 *
165 * @param[in] path The path to convert.
166 * @param[out] url_out pointer to recive the nsurl, The returned url
167 * should be unreferenced by the caller.
168 * @return NSERROR_OK and the url is placed in \a url or error code on
169 * faliure.
170 */
171static nserror posix_path_to_nsurl(const char *path, struct nsurl **url_out)
172{
173 nserror ret;
174 int urllen;
175 char *urlstr;
176 char *escpath; /* escaped version of the path */
177 char *escpaths;
178
179 if ((path == NULL((void*)0)) || (url_out == NULL((void*)0)) || (*path == 0)) {
180 return NSERROR_BAD_PARAMETER;
181 }
182
183 /* escape the path so it can be placed in a url */
184 ret = url_escape(path, false0, "/", &escpath);
185 if (ret != NSERROR_OK) {
186 return ret;
187 }
188 /* remove unecessary / as file: paths are already absolute */
189 escpaths = escpath;
190 while (*escpaths == '/') {
191 escpaths++;
192 }
193
194 /* build url as a string for nsurl constructor */
195 urllen = strlen(escpaths) + FILE_SCHEME_PREFIX_LEN8 + 1;
196 urlstr = malloc(urllen);
197 if (urlstr == NULL((void*)0)) {
198 free(escpath);
199 return NSERROR_NOMEM;
200 }
201
202 snprintf(urlstr, urllen, "%s%s", FILE_SCHEME_PREFIX"file:///", escpaths);
203 free(escpath);
204
205 ret = nsurl_create(urlstr, url_out);
206 free(urlstr);
207
208 return ret;
209}
210
211/**
212 * Ensure that all directory elements needed to store a filename exist.
213 *
214 * @param fname The filename to ensure the path to exists.
215 * @return NSERROR_OK on success or error code on failure.
216 */
217static nserror posix_mkdir_all(const char *fname)
218{
219 char *dname;
220 char *sep;
221 struct stat sb;
222
223 dname = strdup(fname);
224
225 sep = strrchr(dname, '/');
226 if (sep == NULL((void*)0)) {
227 /* no directory separator path is just filename so its ok */
228 free(dname);
229 return NSERROR_OK;
230 }
231
232 *sep = 0; /* null terminate directory path */
233
234 if (stat(dname, &sb) == 0) {
235 free(dname);
236 if (S_ISDIR(sb.st_mode)((((sb.st_mode)) & 0170000) == (0040000))) {
237 /* path to file exists and is a directory */
238 return NSERROR_OK;
239 }
240 return NSERROR_NOT_DIRECTORY;
241 }
242 *sep = '/'; /* restore separator */
243
244 sep = dname;
245 while (*sep == '/') {
246 sep++;
247 }
248 while ((sep = strchr(sep, '/')) != NULL((void*)0)) {
249 *sep = 0;
250 if (stat(dname, &sb) != 0) {
251 if (nsmkdir(dname, S_IRWXU)mkdir((dname), ((0400|0200|0100))) != 0) {
252 /* could not create path element */
253 free(dname);
254 return NSERROR_NOT_FOUND;
255 }
256 } else {
257 if (! S_ISDIR(sb.st_mode)((((sb.st_mode)) & 0170000) == (0040000))) {
258 /* path element not a directory */
259 free(dname);
260 return NSERROR_NOT_DIRECTORY;
261 }
262 }
263 *sep = '/'; /* restore separator */
264 /* skip directory separators */
265 while (*sep == '/') {
266 sep++;
267 }
268 }
269
270 free(dname);
271 return NSERROR_OK;
272}
273
274/**
275 * default to using the posix file handling
276 */
277static struct gui_file_table file_table = {
278 .mkpath = posix_vmkpath,
279 .basename = posix_basename,
280 .nsurl_to_path = posix_nsurl_to_path,
281 .path_to_nsurl = posix_path_to_nsurl,
282 .mkdir_all = posix_mkdir_all,
283};
284
285struct gui_file_table *default_file_table = &file_table;
286
287/* exported interface documented in utils/file.h */
288nserror netsurf_mkpath(char **str, size_t *size, size_t nelm, ...)
289{
290 va_list ap;
291 nserror ret;
292
293 va_start(ap, nelm)__builtin_va_start(ap, nelm);
294 ret = guit->file->mkpath(str, size, nelm, ap);
295 va_end(ap)__builtin_va_end(ap);
296
297 return ret;
298}
299
300/* exported interface documented in utils/file.h */
301nserror netsurf_nsurl_to_path(struct nsurl *url, char **path_out)
302{
303 return guit->file->nsurl_to_path(url, path_out);
304}
305
306/* exported interface documented in utils/file.h */
307nserror netsurf_path_to_nsurl(const char *path, struct nsurl **url)
308{
309 return guit->file->path_to_nsurl(path, url);
310}
311
312/* exported interface documented in utils/file.h */
313nserror netsurf_mkdir_all(const char *fname)
314{
315 return guit->file->mkdir_all(fname);
316}
317
318/* exported interface documented in utils/file.h */
319nserror
320netsurf_recursive_rm(const char *path)
1
[debug] analyzing from netsurf_recursive_rm
321{
322 DIR *parent;
323 struct dirent *entry;
324 nserror ret = NSERROR_OK;
325 struct stat ent_stat; /* stat result of leaf entry */
326
327 parent = opendir(path);
328 if (parent
1.1
'parent' is not equal to NULL
== NULL((void*)0)) {
2
Taking false branch
329 switch (errno(*__errno_location ())) {
330 case ENOENT2:
331 return NSERROR_NOT_FOUND;
332 default:
333 return NSERROR_UNKNOWN;
334 }
335 }
336
337 while ((entry = readdir(parent))) {
3
Loop condition is true. Entering loop body
338 char *leafpath = NULL((void*)0);
339
340 if (strcmp(entry->d_name, ".") == 0 ||
4
Assuming the condition is false
6
Taking false branch
341 strcmp(entry->d_name, "..") == 0)
5
Assuming the condition is false
342 continue;
343
344 ret = netsurf_mkpath(&leafpath, NULL((void*)0), 2, path, entry->d_name);
345 if (ret != NSERROR_OK)
7
Assuming 'ret' is equal to NSERROR_OK
8
Taking false branch
346 goto out;
347
348#if (defined(HAVE_DIRFD) && defined(HAVE_FSTATAT))
349 if (fstatat(dirfd(parent), entry->d_name, &ent_stat,
9
Taking false branch
350 AT_SYMLINK_NOFOLLOW0x100) != 0) {
351#else
352 if (stat(leafpath, &ent_stat) != 0) {
353#endif
354 free(leafpath);
355 goto out_via_errno;
356 }
357 if (S_ISDIR(ent_stat.st_mode)((((ent_stat.st_mode)) & 0170000) == (0040000))) {
10
Assuming the condition is false
11
Taking false branch
358 ret = netsurf_recursive_rm(leafpath);
359 if (ret != NSERROR_OK) {
360 free(leafpath);
361 goto out;
362 }
363 } else {
364#if (defined(HAVE_DIRFD) && defined(HAVE_UNLINKAT))
365 if (unlinkat(dirfd(parent), entry->d_name, 0) != 0) {
12
Assuming that 'dirfd' fails
13
The 1st argument to 'unlinkat' is between -99 and -1 but should be a valid file descriptor or AT_FDCWD
366#else
367 if (unlink(leafpath) != 0) {
368#endif
369 free(leafpath);
370 goto out_via_errno;
371 }
372 }
373
374 free(leafpath);
375 }
376
377 if (rmdir(path) != 0) {
378 goto out_via_errno;
379 }
380
381 goto out;
382
383out_via_errno:
384 switch (errno(*__errno_location ())) {
385 case ENOENT2:
386 ret = NSERROR_NOT_FOUND;
387 break;
388 default:
389 ret = NSERROR_UNKNOWN;
390 }
391out:
392 closedir(parent);
393
394 return ret;
395}