2727 *
2828 * All Rights Reserved.
2929 *
30- * Portions of this software are Copyright (c) 2023-2024 HPC-Gridware GmbH
30+ * Portions of this software are Copyright (c) 2023-2025 HPC-Gridware GmbH
3131 *
3232 ************************************************************************/
3333/* ___INFO__MARK_END__*/
34+ #include < filesystem>
35+ #include < string>
36+
3437#include < cstdio>
3538#include < cstdlib>
3639#include < cstring>
3942#include < cctype>
4043#include < fcntl.h>
4144#include < sys/stat.h>
45+ #include < dirent.h>
4246
4347#ifdef SIGTSTP
4448
6367
6468#include " msg_common.h"
6569
66- static int fd_compare (const void *fd1, const void *fd2);
67-
6870static void sge_close_fd (int fd);
6971
7072/* ***** uti/os/sge_get_pids() *************************************************
@@ -378,6 +380,7 @@ int sge_get_max_fd() {
378380 return sysconf (_SC_OPEN_MAX);
379381}
380382
383+ #if not defined(LINUX) && not defined(SOLARIS)
381384/* ***** uti/os/fd_compare() ****************************************************
382385* NAME
383386* fd_compare() -- file descriptor compare function for qsort()
@@ -431,7 +434,80 @@ static int fd_compare(const void *fd1, const void *fd2) {
431434 }
432435 return 0 ;
433436}
437+ #endif
438+
439+ #if defined(LINUX) || defined(SOLARIS)
440+ /* *
441+ * @brief Get all open file descriptors for a process
442+ *
443+ * Retrieves all currently open file descriptors for the specified process
444+ * by reading the /proc/[pid]/fdinfo directory (or /proc/self/fdinfo if pid is 0).
445+ * The function uses opendir() to iterate over the directory entries and
446+ * automatically excludes the file descriptor used by the directory stream itself.
447+ *
448+ * @param pid Process id (0 for current process/self)
449+ * @return std::set<int> Set containing all open file descriptor numbers
450+ * @note MT-SAFE: get_all_fds() is MT safe
451+ * @see sge_close_all_fds()
452+ */
453+ std::set<int > get_all_fds (pid_t pid) {
454+ DENTER (TOP_LAYER);
434455
456+ std::set<int > fds;
457+
458+ std::filesystem::path proc_path = " /proc/" ;
459+ if (pid == 0 ) {
460+ proc_path += " self/fdinfo" ;
461+ } else {
462+ proc_path += std::to_string (pid) + " /fdinfo" ;
463+ }
464+ #if 0
465+ // Unfortunately, we cannot use the C++ directory_iterator for iterating over /proc/*/fdinfo:
466+ // It opens itself a file handle which shows up in the /proc/*/fdinfo - but we cannot figure
467+ // out which one belongs to the iterator!
468+ // With opendir() we can use dirfd() to get the fd of the directory stream itself and can ignore it.
469+ try {
470+ std::filesystem::directory_iterator iter{proc_path};
471+ for (const auto &entry : iter) {
472+ int fd = std::stoi(entry.path().filename());
473+ fds.insert(fd);
474+ }
475+ } catch (std::filesystem::filesystem_error &e) {
476+ // this should never happen
477+ }
478+ #else
479+ DIR *cwd = opendir (proc_path.c_str ());
480+ if (cwd == nullptr ) {
481+ DPRINTF (" get_all_fds(): cannot open directory %s: %s" , proc_path.c_str (), strerror (errno));
482+ } else {
483+ // Iterate over the directory stream.
484+ // Do not use readdir.2 or readdir_r - they are deprecated.
485+ // See readdir.3 man page.
486+ int cwd_fd = dirfd (cwd);
487+ dirent *dent;
488+ while ((dent = readdir (cwd)) != nullptr ) {
489+ if (dent->d_name [0 ] == ' \0 ' ) {
490+ DPRINTF (" get_all_fds(): empty filename in directory %s" , proc_path.c_str ());
491+ } else if (strcmp (dent->d_name , " .." ) == 0 || strcmp (dent->d_name , " ." ) == 0 ) {
492+ DPRINTF (" get_all_fds(): skipping %s" , dent->d_name );
493+ } else {
494+ int fd = std::stoi (dent->d_name );
495+ if (fd == cwd_fd) {
496+ DPRINTF (" get_all_fds(): skipping opendir() internal fd %d" , fd);
497+ } else {
498+ // This is a valid fd, store it in the returned set.
499+ fds.insert (fd);
500+ }
501+ }
502+ }
503+ closedir (cwd);
504+ }
505+
506+ #endif
507+
508+ DRETURN (fds);
509+ }
510+ #endif
435511
436512/* ***** uti/os/sge_close_fd() **************************************************
437513* NAME
@@ -455,43 +531,74 @@ static int fd_compare(const void *fd1, const void *fd2) {
455531* uti/os/sge_close_all_fds()
456532*******************************************************************************/
457533static void sge_close_fd (int fd) {
534+ DENTER (TOP_LAYER);
458535#ifdef __INSURE__
459536 if (_insure_is_internal_fd (fd)) {
460537 return ;
461538 }
462539#endif
463- close (fd);
540+ #if 1
541+ if (close (fd) == -1 ) {
542+ DPRINTF (" close(%d) failed: %s\n " , fd, strerror (errno));
543+ }
544+ #else
545+ // @todo When closing all fds is called for fork()/exec(), we could just set the FD_CLOEXEC flag
546+ // on the file descriptor. This will not close the fd, but just mark it to be closed on any
547+ // of the various exec() calls.
548+ // A short test showed that setting the flag instead of closing the fd is about 30% faster.
549+ // It would be worth doing only if the caller has a high number of file descriptors open,
550+ // which is not the case on the execution side, but might be in sge_qmaster,
551+ // e.g., when starting a JSV script in a big cluster.
552+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
553+ DPRINTF("fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s\n", fd, strerror(errno));
554+ }
555+ #endif
556+ DRETURN_VOID;
557+ }
558+
559+ /* *
560+ * @brief close all open file descriptors
561+ *
562+ * This function is used to close all open file descriptors for
563+ * the current process.
564+ *
565+ * It is possible to pass a list (int array) of file descriptors which shall
566+ * be kept open.
567+ *
568+ * The algorithm used depends on the operating system:
569+ * * On Linux and Solaris we can figure out the actually open file descriptors
570+ * by looping over all files in `/proc/self/fdinfo` and closing them.
571+ * * On other OSes we loop from 0 to the maximum possible file descriptor
572+ * and (try to) close them.
573+ * * On newer Linux versions we could possibly use the `close_range()` function,
574+ * ideally using the flag CLOSE_RANGE_CLOEXEC which will not immediately close
575+ * the fd but just mark it to be closed on `exec()` calls - which is
576+ * a situation where we call `close_all_fds()`.
577+ * But it is not yet available on our default build platform, CentOS 8.
578+ *
579+ * @param keep_open - optionally: int array of file descriptors to keep open
580+ * @param nr_of_keep_open_entries - number of entries in keep_open
581+ * @todo What about fcntl(fd, F_SETFD, FD_CLOEXEC)? See comment in sge_close_fd().
582+ */
583+ #if defined(LINUX) || defined(SOLARIS)
584+ static bool keep_fd_open (int *keep_open, unsigned long nr_of_keep_open_entries, int fd) {
585+ for (unsigned long keep_open_array_index = 0 ; keep_open_array_index < nr_of_keep_open_entries; keep_open_array_index++) {
586+ if (keep_open[keep_open_array_index] == fd) {
587+ return true ;
588+ }
589+ }
590+ return false ;
464591}
465592
466- /* ***** uti/os/sge_close_all_fds() *********************************************
467- * NAME
468- * sge_close_all_fds() -- close all open file descriptors
469- *
470- * SYNOPSIS
471- * void sge_close_all_fds(int* keep_open, unsigned long nr_of_fds)
472- *
473- * FUNCTION
474- * This function is used to close all possible open file descriptors for
475- * the current process. This is done by getting the max. possible file
476- * descriptor count and looping over all file descriptors and calling
477- * sge_close_fd().
478- *
479- * It is possible to specify a file descriptor set which should not be
480- * closed.
481- *
482- * INPUTS
483- * int* keep_open - integer array which contains file descriptor
484- * ids which should not be closed
485- * - if this value is set to nullptr nr_of_fds is
486- * ignored
487- * unsigned long nr_of_fds - nr of filedescriptors in the keep_open array
488- *
489- * RESULT
490- * void - no result
491- *
492- * SEE ALSO
493- * uti/os/sge_close_fd()
494- *******************************************************************************/
593+ void sge_close_all_fds (int *keep_open, unsigned long nr_of_keep_open_entries) {
594+ std::set all_fds = get_all_fds ();
595+ for (auto fd : all_fds) {
596+ if (keep_open == nullptr || !keep_fd_open (keep_open, nr_of_keep_open_entries, fd)) {
597+ sge_close_fd (fd);
598+ }
599+ }
600+ }
601+ #else
495602void sge_close_all_fds (int *keep_open, unsigned long nr_of_keep_open_entries) {
496603 int maxfd = sge_get_max_fd ();
497604 if (keep_open == nullptr ) {
@@ -533,6 +640,7 @@ void sge_close_all_fds(int *keep_open, unsigned long nr_of_keep_open_entries) {
533640 }
534641 }
535642}
643+ #endif
536644
537645/* ***** uti/os/sge_dup_fd_above_stderr() **************************************
538646* NAME
0 commit comments