View Issue Details

IDProjectCategoryView StatusLast Update
00019311003.1(2024)/Issue8System Interfacespublic2025-08-07 10:20
Reporterdag-erling Assigned Togeoffclare  
PrioritynormalSeverityEditorialTypeClarification Requested
Status AppliedResolutionAccepted As Marked 
NameDag-Erling Smørgrav
Organization
User Reference
SectionSystem Interfaces
Page Number614
Line Number21757-21763, 21770-21772
Interp Status---
Final Accepted Text0001931:0007222
Summary0001931: Behavior of scandir() when no entries are selected
DescriptionThe description of `scandir()` does not specify whether `namelist` is expected to be null when no directory entries are selected (i.e. `scandir()` returns 0).

The BSD implementation of `scandir()` allocates the pointer array before reading the directory and returns a non-null pointer which must be freed even if no entries were selected. This has been the case since the initial CSRG version in 1982. In macOS, which uses a direct descendant of this implementation, this is even documented in the manual page. The only difference between the CSRG, FreeBSD, and macOS implementations is the size of this initial allocation: the original implementation tried to guess how many pointers would be needed if all entries were selected, while FreeBSD, and later macOS, switched to an initial allocation size of 32 pointers. Solaris also appears to use a descendant of the CSRG implementation and still tries to preallocate the maximum number of pointers.

Both glibc and musl, on the other hand, delay the allocation until the first entry is selected, so if `scandir()` returns 0, `namelist` is null.

My main worry is that developers working primarily on Linux may treat the return value as trinary and return early if it is 0, failing to free `namelist`, not realizing that this will result in a memory leak when their code is run on e.g. FreeBSD.

I'll also note that the code example in the specification frees `namelist` unconditionally as long as `scandir()` does not fail, but this does not really answer the question since a) the example calls `scandir()` without a selection function, so we can expect at least two entries for `.` and `..`, and b) `free(NULL)` is perfectly safe.
Desired ActionEither clarify whether `namelist` is expected to be null when `scandir()` returns 0, or explicitly leave it unspecified.
Tagstc1-2024

Activities

dag-erling

2025-06-21 10:18

reporter   bugnote:0007205

I accidentally filed this in “Issue 8 drafts” rather than “Issue 8”, please move if possible.

geoffclare

2025-07-17 16:12

manager   bugnote:0007222

Change lines 21757-21763 from:
The scandir( ) function shall scan the directory dir, calling the function referenced by sel on each directory entry. Entries for which the function referenced by sel returns non-zero shall be stored in strings allocated as if by a call to malloc( ), and sorted as if by a call to qsort( ) with the comparison function compar, except that compar need not provide total ordering. The strings are collected in array namelist which shall be allocated as if by a call to malloc( ). If sel is a null pointer, all entries shall be selected. If the comparison function compar does not provide total ordering, the order in which the directory entries are stored is unspecified.

to:
The scandir( ) function shall scan the directory dir, calling the function referenced by sel on each directory entry. Entries for which the function referenced by sel returns non-zero shall be stored in dirent structures allocated as if by a call to malloc( ), and sorted as if by a call to qsort( ) with the comparison function compar, except that compar need not provide total ordering. The dirent structures are collected in the array pointed to by namelist which shall be allocated as if by a call to malloc( ) if at least one entry is selected by sel; if no entries are selected, it is unspecified whether the array is allocated as if by a call to malloc or the value pointed to by namelist is set to a null pointer. If sel is a null pointer, all entries shall be selected. If the comparison function compar does not provide total ordering, the order in which the directory entries are stored is unspecified.


Change the example lines 21799-21815 to:
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
...
struct dirent **files;
int i,n;

n = scandir(".", &files, NULL, alphasort);
if (n < 0)
    perror("scandir");
else {
    for (i = 0; i < n; i++) {
        printf("%s\n", files[i]->d_name);
        free(files[i]);
    }
}
if (files)
    free(files);
...

Page 615 lines 21826-21828, change:
For functions that allocate memory as if by malloc( ), the application should release such memory when it is no longer required by a call to free( ). For scandir( ), this is namelist (including all of the individual strings in namelist).

to:
For functions that allocate memory as if by malloc( ), the application should deallocate such memory when it is no longer required by a call to free( ). For scandir( ), this is the array pointed to by namelist (including all of the individual dirent structures in the array).

lanodan

2025-07-21 12:03

reporter   bugnote:0007224

Last edited: 2025-07-24 15:02

Moved to https://austingroupbugs.net/view.php?id=1938 as suggested by Geoff Clare on the mailing-list

Issue History

Date Modified Username Field Change
2025-06-21 10:16 dag-erling New Issue
2025-06-21 10:18 dag-erling Note Added: 0007205
2025-06-24 09:23 geoffclare Project Issue 8 drafts => 1003.1(2024)/Issue8
2025-07-17 16:12 geoffclare Note Added: 0007222
2025-07-17 16:13 geoffclare Status New => Resolved
2025-07-17 16:13 geoffclare Resolution Open => Accepted As Marked
2025-07-17 16:13 geoffclare Interp Status => ---
2025-07-17 16:13 geoffclare Final Accepted Text => 0001931:0007222
2025-07-17 16:13 geoffclare Tag Attached: tc1-2024
2025-07-21 12:03 lanodan Note Added: 0007224
2025-07-24 15:02 lanodan Note Edited: 0007224
2025-08-05 11:10 geoffclare Assigned To => geoffclare
2025-08-07 10:20 geoffclare Status Resolved => Applied