Coverage Report

Created: 2026-03-31 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dcmtk/ofstd/libsrc/offilsys.cc
Line
Count
Source
1
/*
2
 *
3
 *  Copyright (C) 2021-2026, OFFIS e.V.
4
 *  All rights reserved.  See COPYRIGHT file for details.
5
 *
6
 *  This software and supporting documentation were developed by
7
 *
8
 *    OFFIS e.V.
9
 *    R&D Division Health
10
 *    Escherweg 2
11
 *    D-26121 Oldenburg, Germany
12
 *
13
 *
14
 *  Module:  ofstd
15
 *
16
 *  Author:  Jan Schlamelcher
17
 *
18
 *  Purpose: Implementing the currently used subset of C++17' std::filesystem
19
 *
20
 */
21
22
#include "dcmtk/config/osconfig.h"
23
24
#include <cstring>
25
#include <cassert>
26
#include <climits>
27
28
#include "dcmtk/ofstd/offilsys.h"
29
#include "dcmtk/ofstd/ofstd.h"  /* for READDIR_IS_THREADSAFE */
30
31
#ifdef _WIN32
32
33
#include <io.h>
34
35
#else // _WIN32
36
37
BEGIN_EXTERN_C
38
#include <sys/types.h>
39
#ifdef HAVE_SYS_FILE_H
40
#include <sys/file.h>  // for struct DIR, opendir()
41
#endif
42
#ifdef HAVE_DIRENT_H
43
#include <dirent.h>    // for struct DIR, opendir()
44
#endif
45
END_EXTERN_C
46
47
#endif // _WIN32
48
49
OFpath::OFpath()
50
0
: m_NativeString()
51
0
{
52
53
0
}
54
55
OFpath::OFpath( const OFpath& rhs )
56
0
: m_NativeString( rhs.native() )
57
0
{
58
59
0
}
60
61
OFpath::OFpath( OFrvalue_ref(OFpath) rhs )
62
0
: m_NativeString()
63
0
{
64
0
    m_NativeString.swap( OFrvalue_access( rhs ).m_NativeString );
65
0
}
66
67
OFpath::OFpath( const char* const cstr, format fmt )
68
0
: m_NativeString( cstr )
69
0
{
70
#ifdef _WIN32
71
    convertSeparator(fmt);
72
#endif
73
0
}
74
75
OFpath::OFpath( const OFString& string, format fmt )
76
0
: m_NativeString( string )
77
0
{
78
#ifdef _WIN32
79
    convertSeparator(fmt);
80
#endif
81
0
}
82
83
#ifdef _WIN32
84
void OFpath::convertSeparator( format fmt )
85
{
86
    size_t pos = OFString_npos;
87
    switch( fmt )
88
    {
89
    case native_format:
90
        return;
91
    case auto_format:
92
        pos = m_NativeString.find_first_of( "\\/" );
93
        if( pos == OFString_npos || m_NativeString[pos] == preferred_separator )
94
            return;
95
        break;
96
    default:
97
    case generic_format:
98
        pos = m_NativeString.find( '/' );
99
        if( pos == OFString_npos )
100
            return;
101
        break;
102
    }
103
    m_NativeString[pos] = preferred_separator;
104
    for( pos = m_NativeString.find( '/', pos + 1 ); pos != OFString_npos; pos = m_NativeString.find( '/', pos + 1 ) )
105
        m_NativeString[pos] = preferred_separator;
106
}
107
#endif
108
109
OFpath& OFpath::operator=( const OFpath& rhs )
110
0
{
111
0
    m_NativeString = rhs.native();
112
0
    return *this;
113
0
}
114
115
OFpath& OFpath::operator=( OFrvalue_ref(OFpath) rhs )
116
0
{
117
0
    if( this != &OFrvalue_access( rhs ) )
118
0
    {
119
0
        m_NativeString.clear();
120
0
        m_NativeString.swap( OFrvalue_access( rhs ).m_NativeString );
121
0
    }
122
0
    return *this;
123
0
}
124
125
OFBool OFpath::empty() const
126
0
{
127
0
    return m_NativeString.empty();
128
0
}
129
130
OFBool OFpath::is_absolute() const
131
0
{
132
#if _WIN32
133
    const std::size_t pos = findRootName();
134
    return OFString_npos != pos &&
135
    (
136
        ( pos + 1 ) == m_NativeString.size() ||
137
        preferred_separator == m_NativeString[pos+1]
138
    );
139
#else
140
0
    return has_root_directory();
141
0
#endif
142
0
}
143
144
OFBool OFpath::is_relative() const
145
0
{
146
0
    return !is_absolute();
147
0
}
148
149
OFBool OFpath::has_root_name() const
150
0
{
151
0
    return OFString_npos != findRootName();
152
0
}
153
154
OFBool OFpath::has_root_directory() const
155
0
{
156
#if _WIN32
157
    size_t pos = findRootName();
158
    pos = ( OFString_npos != pos ? pos + 1 : 0 );
159
    return pos < m_NativeString.size() && preferred_separator == m_NativeString[pos];
160
#else
161
0
    return !empty() && preferred_separator == *m_NativeString.begin();
162
0
#endif
163
0
}
164
165
OFBool OFpath::has_filename() const
166
0
{
167
#if _WIN32
168
    OFString::const_iterator it = m_NativeString.end();
169
    if( it == m_NativeString.begin() )
170
        return OFFalse;
171
    --it;
172
    if( *it == preferred_separator )
173
        return OFFalse;
174
    if( *it != ':' )
175
        return OFTrue;
176
    while( it != m_NativeString.begin() )
177
        if( *--it == preferred_separator )
178
            return OFTrue;
179
    return OFFalse;
180
#else
181
0
    return !empty() && preferred_separator != *(m_NativeString.end() - 1);
182
0
#endif
183
0
}
184
185
186
OFBool OFpath::has_extension() const
187
0
{
188
0
    return OFString_npos != findExtension();
189
0
}
190
191
const OFString& OFpath::native() const
192
0
{
193
0
    return m_NativeString;
194
0
}
195
196
const char* OFpath::c_str() const
197
0
{
198
0
    return m_NativeString.c_str();
199
0
}
200
201
OFrvalue<OFpath> OFpath::root_name() const
202
0
{
203
#if _WIN32
204
    const size_t pos = findRootName();
205
    if( OFString_npos != pos )
206
        return OFpath( m_NativeString.substr( 0, pos + 1 ) );
207
#endif
208
0
    return OFpath();
209
0
}
210
211
OFrvalue<OFpath> OFpath::filename() const
212
0
{
213
0
    const size_t pos = findFilename();
214
0
    if( OFString_npos != pos )
215
0
        return OFpath( m_NativeString.substr( pos ) );
216
0
    return OFpath();
217
0
}
218
219
OFrvalue<OFpath> OFpath::extension() const
220
0
{
221
0
    const size_t pos = findExtension();
222
0
    if( OFString_npos != pos )
223
0
        return OFpath( m_NativeString.substr( pos ) );
224
0
    return OFpath();
225
0
}
226
227
OFpath& OFpath::operator/=( const OFpath& rhs )
228
0
{
229
    // self append
230
0
    if( this == &rhs )
231
0
        return *this /= OFpath( rhs );
232
#if _WIN32
233
    // Comments are the descriptions from en.cppreference.com, put to whatever code segment handles it:
234
    // If p.is_absolute() || (p.has_root_name() && p.root_name() != root_name()),
235
    // then replaces the current path with p as if by operator=(p) and finishes.
236
    std::size_t pos = rhs.findRootName();
237
    if( OFString_npos != pos ) // .. p.has_root_name()
238
    {
239
        ++pos;
240
        if
241
        (
242
            // .. p.is_absolute()
243
            ( pos < rhs.m_NativeString.size() && rhs.m_NativeString[pos] == preferred_separator ) ||
244
            // .. p.root_name() != root_name()
245
            ( pos > m_NativeString.size() || 0 != m_NativeString.compare( 0, pos - 1, rhs.m_NativeString, 0, pos - 1 ) )
246
        )
247
        {
248
            // then replaces the current path with p as if by operator=(p) and finishes.
249
            m_NativeString = rhs.m_NativeString;
250
            return *this;
251
        }
252
    }
253
    else pos = 0;
254
    // Otherwise, if p.has_root_directory(), then removes any root directory and the
255
    // entire relative path from the generic format pathname of *this, then appends the native format
256
    // pathname of p, omitting any root-name from its generic format, to the native format of *this.
257
    if( pos < rhs.m_NativeString.size() && rhs.m_NativeString[pos] == '\\' ) // .. p.has_root_directory()
258
    {
259
        // we shall remove ONLY the root directory and relative path, not the root name
260
        // so find it and, if it exists, keep it
261
        const size_t root = findRootName();
262
        if( OFString_npos == root )
263
        {
264
            // no root name, so replace the entire string
265
            m_NativeString = rhs.m_NativeString;
266
        }
267
        else
268
        {
269
            // removes any root directory and the entire relative path from the generic format pathname of *this
270
            // appends the native format pathname of p, omitting any root-name from its generic format
271
            m_NativeString.replace( root + 1, OFString_npos, rhs.m_NativeString, pos, OFString_npos );
272
        }
273
    }
274
    else
275
    {
276
        // If has_filename() || (!has_root_directory() && is_absolute())
277
        if( !empty() && preferred_separator != *(m_NativeString.end() - 1) )
278
        {
279
            // then appends path::preferred_separator to the generic format of *this
280
            m_NativeString.reserve( m_NativeString.size() + rhs.m_NativeString.size() - pos + 1 );
281
            m_NativeString += preferred_separator;
282
        }
283
        // appends the native format pathname of p, omitting any root-name from its generic format
284
        m_NativeString += rhs.m_NativeString.substr( pos );
285
    }
286
#else
287
    // version for filesystems without root names, pretty straight forward
288
0
    if( !rhs.is_absolute() )
289
0
    {
290
0
        if( has_filename() )
291
0
        {
292
0
            m_NativeString.reserve( m_NativeString.size() + rhs.m_NativeString.size() + 1 );
293
0
            m_NativeString += preferred_separator;
294
0
        }
295
0
        m_NativeString += rhs.m_NativeString;
296
0
    }
297
0
    else m_NativeString = rhs.m_NativeString;
298
0
#endif
299
0
    return *this;
300
0
}
301
302
size_t OFpath::findRootName() const
303
0
{
304
#if _WIN32
305
    const size_t pos = m_NativeString.find_first_of( ":\\" );
306
    if( OFString_npos != pos && m_NativeString[pos] == ':' )
307
        return pos;
308
#endif
309
0
    return OFString_npos;
310
0
}
311
312
size_t OFpath::findFilename() const
313
0
{
314
#if _WIN32
315
    const size_t pos = m_NativeString.find_last_of( ":\\" );
316
#else
317
0
    const size_t pos = m_NativeString.find_last_of( "/" );
318
0
#endif
319
0
    if( pos == OFString_npos )
320
0
        return empty() ? OFString_npos : 0;
321
0
    else
322
0
        return pos < m_NativeString.size() ? pos + 1 : OFString_npos;
323
0
}
324
325
size_t OFpath::findExtension() const
326
0
{
327
#if _WIN32
328
    const size_t pos = m_NativeString.find_last_of( ".:\\" );
329
#else
330
0
    const size_t pos = m_NativeString.find_last_of( "./" );
331
0
#endif
332
0
    if
333
0
    (
334
0
        pos && OFString_npos != pos && m_NativeString[pos] == '.'
335
0
    )
336
0
    {
337
0
        switch( m_NativeString[pos-1] )
338
0
        {
339
#if _WIN32
340
        case '\\':
341
        case ':':
342
#else
343
0
        case '/':
344
0
#endif
345
0
            return OFString_npos;
346
0
        case '.':
347
0
            if( pos < m_NativeString.size() - 1 )
348
0
                return pos;
349
0
            if( pos == 1 )
350
0
                return OFString_npos;
351
0
            switch( m_NativeString[pos-2] )
352
0
            {
353
#if _WIN32
354
            case '\\':
355
            case ':':
356
#else
357
0
            case '/':
358
0
#endif
359
0
                return OFString_npos;
360
0
            default:
361
0
                break;
362
0
            }
363
0
        default:
364
0
            break;
365
0
        }
366
0
        return pos;
367
0
    }
368
0
    return OFString_npos;
369
0
}
370
371
const OFpath& OFdirectory_entry::path() const
372
0
{
373
0
    return m_Path;
374
0
}
375
376
OFdirectory_entry::operator const OFpath&() const
377
0
{
378
0
    return OFdirectory_entry::path();
379
0
}
380
381
OFdirectory_iterator_proxy::OFdirectory_iterator_proxy( const OFdirectory_entry& rhs )
382
0
: m_Entry( rhs )
383
0
{
384
385
0
}
386
387
class OFdirectory_iterator::NativeDirectoryEntry : public OFdirectory_entry
388
{
389
public:
390
    NativeDirectoryEntry( const OFpath& path );
391
    ~NativeDirectoryEntry();
392
    OFBool skipInvalidFiles();
393
    OFBool next();
394
395
private:
396
    OFBool good() const;
397
    const char* filename() const;
398
    const OFpath m_Parent;
399
400
#ifdef HAVE__FINDFIRST // HAVE__FINDFIRST
401
    ::_finddata_t m_FileData;
402
    OFintptr_t m_hFile;
403
#else // HAVE__FINDFIRST
404
    ::DIR* m_pDIR;
405
    ::dirent* m_pDirent;
406
#if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE)
407
    Uint8 m_Buffer[sizeof(::dirent) + _POSIX_PATH_MAX + 1];
408
#endif
409
#endif // HAVE__FINDFIRST
410
};
411
412
OFdirectory_iterator::NativeDirectoryEntry::NativeDirectoryEntry( const OFpath& path )
413
0
: OFdirectory_entry()
414
0
, m_Parent( path )
415
#ifdef HAVE__FINDFIRST
416
, m_FileData()
417
, m_hFile( _findfirst( OFconst_cast( char*, (path / "*").c_str() ), &m_FileData ) )
418
#else // HAVE__FINDFIRST
419
0
, m_pDIR( ::opendir( path.c_str() ) )
420
, m_pDirent()
421
#if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE)
422
, m_Buffer()
423
#endif
424
#endif // HAVE__FINDFIRST
425
0
{
426
0
#ifndef HAVE__FINDFIRST
427
0
    if( m_pDIR && !next() )
428
0
    {
429
0
        ::closedir( m_pDIR );
430
0
        m_pDIR = OFnullptr;
431
0
    }
432
0
#endif
433
0
}
434
435
OFdirectory_iterator::NativeDirectoryEntry::~NativeDirectoryEntry()
436
0
{
437
#ifdef HAVE__FINDFIRST
438
    ::_findclose( m_hFile );
439
#else
440
0
    if( m_pDIR ) ::closedir( m_pDIR );
441
0
#endif
442
0
}
443
444
OFBool OFdirectory_iterator::NativeDirectoryEntry::good() const
445
0
{
446
#ifdef HAVE__FINDFIRST
447
    return -1 != m_hFile;
448
#else
449
0
    return m_pDIR;
450
0
#endif
451
0
}
452
453
const char* OFdirectory_iterator::NativeDirectoryEntry::filename() const
454
0
{
455
#ifdef HAVE__FINDFIRST
456
    return m_FileData.name;
457
#else
458
0
    return m_pDirent->d_name;
459
0
#endif
460
0
}
461
462
OFBool OFdirectory_iterator::NativeDirectoryEntry::next()
463
0
{
464
#ifdef HAVE__FINDFIRST // HAVE__FINDFIRST
465
    return !::_findnext( m_hFile, &m_FileData );
466
#else // HAVE__FINDFIRST
467
#if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE)
468
    return !::readdir_r( m_pDIR, OFreinterpret_cast(dirent*, m_Buffer ), &m_pDirent ) && m_pDirent;
469
#else // HAVE_READDIR_R && !READDIR_IS_THREADSAFE
470
0
    return (m_pDirent = ::readdir( m_pDIR ) );
471
0
#endif // HAVE_READDIR_R && !READDIR_IS_THREADSAFE
472
0
#endif // HAVE__FINDFIRST
473
0
}
474
475
OFBool OFdirectory_iterator::NativeDirectoryEntry::skipInvalidFiles()
476
0
{
477
0
    while( good() && ( !::strcmp( filename(), "." ) || !::strcmp( filename(), ".." ) ) )
478
0
    {
479
0
        if( !next() )
480
0
            return OFFalse;
481
0
    }
482
0
    if( good() )
483
0
    {
484
0
        OFdirectory_entry::m_Path = m_Parent / filename();
485
0
        return OFTrue;
486
0
    }
487
0
    return OFFalse;
488
0
}
489
490
OFdirectory_iterator::OFdirectory_iterator()
491
0
: m_pEntry()
492
0
{
493
494
0
}
495
496
OFdirectory_iterator::OFdirectory_iterator( const OFpath& path )
497
0
: m_pEntry( new NativeDirectoryEntry( path ) )
498
0
{
499
0
    assert( m_pEntry );
500
0
    if( !m_pEntry->skipInvalidFiles() )
501
0
        m_pEntry.reset();
502
0
}
503
504
OFdirectory_iterator::OFdirectory_iterator( const OFdirectory_iterator& rhs )
505
0
: m_pEntry( rhs.m_pEntry )
506
0
{
507
508
0
}
509
510
OFdirectory_iterator& OFdirectory_iterator::operator=( const OFdirectory_iterator& rhs )
511
0
{
512
0
    m_pEntry = rhs.m_pEntry;
513
0
    return *this;
514
0
}
515
516
OFdirectory_iterator::~OFdirectory_iterator()
517
0
{
518
519
0
}
520
521
OFBool OFdirectory_iterator::operator==( const OFdirectory_iterator& rhs ) const
522
0
{
523
0
    return m_pEntry == rhs.m_pEntry;
524
0
}
525
526
OFBool OFdirectory_iterator::operator!=( const OFdirectory_iterator& rhs ) const
527
0
{
528
0
    return m_pEntry != rhs.m_pEntry;
529
0
}
530
531
OFdirectory_iterator& OFdirectory_iterator::operator++()
532
0
{
533
0
    assert( m_pEntry );
534
0
    if( !m_pEntry->next() || !m_pEntry->skipInvalidFiles() )
535
0
        m_pEntry.reset();
536
0
    return *this;
537
0
}
538
539
OFrvalue<OFdirectory_iterator_proxy> OFdirectory_iterator::operator++(int)
540
0
{
541
0
    assert( m_pEntry );
542
0
    OFdirectory_iterator_proxy proxy( *m_pEntry );
543
0
    ++(*this);
544
0
    return proxy;
545
0
}
546
547
OFdirectory_iterator::pointer OFdirectory_iterator::operator->() const
548
0
{
549
0
    assert( m_pEntry );
550
0
    return m_pEntry.get();
551
0
}
552
553
OFdirectory_iterator::reference OFdirectory_iterator::operator*() const
554
0
{
555
    assert( m_pEntry );
556
0
    return *m_pEntry;
557
0
}