Skip to content

Commit e28f746

Browse files
authored
Merge pull request #1075 from darshan-hpc/dev-fcntl-dup-wrap-wrappers
Wrap fcntl() calls
2 parents b30ce48 + e522f13 commit e28f746

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

darshan-runtime/lib/darshan-posix.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ DARSHAN_FORWARD_DECL(creat64, int, (const char* path, mode_t mode));
5555
DARSHAN_FORWARD_DECL(dup, int, (int oldfd));
5656
DARSHAN_FORWARD_DECL(dup2, int, (int oldfd, int newfd));
5757
DARSHAN_FORWARD_DECL(dup3, int, (int oldfd, int newfd, int flags));
58+
DARSHAN_FORWARD_DECL(fcntl, int, (int, int, ...));
59+
DARSHAN_FORWARD_DECL(fcntl64, int, (int, int, ...));
5860
DARSHAN_FORWARD_DECL(fileno, int, (FILE *stream));
5961
DARSHAN_FORWARD_DECL(mkstemp, int, (char *template));
6062
DARSHAN_FORWARD_DECL(mkostemp, int, (char *template, int flags));
@@ -787,6 +789,85 @@ int DARSHAN_DECL(dup3)(int oldfd, int newfd, int flags)
787789
return(ret);
788790
}
789791

792+
/* wrapping fcntl is a little strange for two related reasons:
793+
* - the code can do a lot of different things based on 'cmd'
794+
* - some of those 'cmd' (not all) take a third argument
795+
*
796+
* There are some worrying notes in the documentation about calling va_args
797+
* when there's no third (variable) argument, but we observed glibc doing that
798+
* and our testing seems to indicate it's ok to do so.
799+
*
800+
* So we'll always grab the variable argument, even if it's not there, and
801+
* always pass whatever we get to the real fcntl. Then we'll figure out if the
802+
* command was something we should log or not, and update our stats accordingly
803+
*/
804+
805+
int DARSHAN_DECL(fcntl)(int fd, int cmd, ...)
806+
{
807+
int ret;
808+
double tm1, tm2;
809+
va_list arg;
810+
void *next;
811+
812+
MAP_OR_FAIL(fcntl);
813+
814+
va_start(arg, cmd);
815+
next = va_arg(arg, void*);
816+
va_end(arg);
817+
818+
tm1 = POSIX_WTIME();
819+
ret = __real_fcntl(fd, cmd, next);
820+
tm2 = POSIX_WTIME();
821+
822+
struct posix_file_record_ref *rec_ref;
823+
824+
/* some code (e.g. python) prefers (portabilty? functionality?) to
825+
* duplicate the file descriptor via fcntl instead of dup/dup2/dup3 */
826+
if (ret >= 0 && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC)) {
827+
POSIX_PRE_RECORD();
828+
rec_ref = darshan_lookup_record_ref(posix_runtime->fd_hash,
829+
&fd, sizeof(fd));
830+
831+
POSIX_RECORD_REFOPEN(ret, rec_ref, tm1, tm2, POSIX_DUPS);
832+
POSIX_POST_RECORD();
833+
}
834+
835+
return ret;
836+
}
837+
838+
int DARSHAN_DECL(fcntl64)(int fd, int cmd, ...)
839+
{
840+
int ret;
841+
double tm1, tm2;
842+
va_list arg;
843+
void *next;
844+
845+
MAP_OR_FAIL(fcntl64);
846+
847+
va_start(arg, cmd);
848+
next = va_arg(arg, void*);
849+
va_end(arg);
850+
851+
tm1 = POSIX_WTIME();
852+
ret = __real_fcntl64(fd, cmd, next);
853+
tm2 = POSIX_WTIME();
854+
855+
struct posix_file_record_ref *rec_ref;
856+
857+
/* this is a duplicate of code in fcntl, but when I try to break this out
858+
* into a separate 'fcntl_common' routine, POSIX_PRE_RECORD() macro cannot
859+
* find the __darshan_disabled variable, and furthermore I get weird
860+
* "failed to seek" errors. Am I going to have to make this a macro? */
861+
if (ret >= 0 && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC)) {
862+
POSIX_PRE_RECORD();
863+
rec_ref = darshan_lookup_record_ref(posix_runtime->fd_hash,
864+
&fd, sizeof(fd));
865+
POSIX_RECORD_REFOPEN(ret, rec_ref, tm1, tm2, POSIX_DUPS);
866+
POSIX_POST_RECORD();
867+
}
868+
869+
return ret;
870+
}
790871
int DARSHAN_DECL(fileno)(FILE *stream)
791872
{
792873
int ret;

darshan-runtime/share/ld-opts/darshan-posix-ld-opts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
--wrap=dup
99
--wrap=dup2
1010
--wrap=dup3
11+
--wrap=fcntl
12+
--wrap=fcntl64
1113
--wrap=mkstemp
1214
--wrap=mkostemp
1315
--wrap=mkstemps
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
3+
from mpi4py import MPI
4+
import numpy as np
5+
import os
6+
7+
def main():
8+
comm = MPI.COMM_WORLD
9+
arr1 = np.arange(50)
10+
np.save('single_array.npy', arr1)
11+
os.remove('single_array.npy')
12+
13+
14+
if __name__ == "__main__":
15+
main()

darshan-test/python_runtime_tests.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,63 @@ def test_forked_process_mpi(tmpdir):
231231
# regression test for gh-786, mpi version
232232
darshan_install_path = os.environ.get("DARSHAN_INSTALL_PATH")
233233
do_forked_process_test(tmpdir, darshan_install_path)
234+
235+
def test_os_dup(tmpdir):
236+
# numpy calls Python's os.dup() which in turn was duplicating a file
237+
# descriptor via fcntl
238+
n_ranks = 1
239+
root_path = os.environ.get("DARSHAN_ROOT_PATH")
240+
darshan_install_path = os.environ.get("DARSHAN_INSTALL_PATH")
241+
test_script_path = os.path.join(root_path,
242+
"darshan-test",
243+
"python_mpi_scripts",
244+
"runtime_prog_issue_1072.py")
245+
darshan_lib_path = os.path.join(darshan_install_path,
246+
"lib",
247+
"libdarshan.so")
248+
hdf5_lib_path = os.environ.get("HDF5_LIB")
249+
250+
with tmpdir.as_cwd():
251+
cwd = os.getcwd()
252+
subprocess.check_output(["mpirun",
253+
"--allow-run-as-root",
254+
"-n",
255+
f"{n_ranks}",
256+
"-x",
257+
f"LD_PRELOAD={darshan_lib_path}:{hdf5_lib_path}",
258+
"-x",
259+
f"DARSHAN_LOGPATH={cwd}",
260+
"python",
261+
f"{test_script_path}"])
262+
263+
log_file_list = glob.glob("*.darshan")
264+
# only a single log file should be generated
265+
# by darshan
266+
assert len(log_file_list) == 1
267+
path_to_log = os.path.join(cwd, log_file_list[0])
268+
# numpy will read a bunch of python files but we only care about the
269+
# numpy file
270+
target_filename = "single_array.npy"
271+
272+
# common stuff done. Real check for the "dup via fcntl" issue
273+
274+
io_module = "POSIX"
275+
key = "POSIX_BYTES_WRITTEN"
276+
277+
# check if log file exists
278+
if not os.path.isfile(path_to_log) :
279+
print(f"Darshan log file '{path_to_log}' does not exist")
280+
else :
281+
print(f"Darshan log file '{path_to_log}'")
282+
283+
try:
284+
with darshan.DarshanReport(path_to_log, filter_patterns=[target_filename], filter_mode='include') as report:
285+
# numpy will write the header (128 bytes) out via posix...
286+
posix_records = report.records["POSIX"].to_dict()
287+
# but flush its payload (50 8 byte ints: 400 bytes) with stdio (flush at fclose)
288+
stdio_records = report.records["STDIO"].to_dict()
289+
posix_bytes = posix_records[0]["counters"]["POSIX_BYTES_WRITTEN"]
290+
stdio_bytes = stdio_records[0]["counters"]["STDIO_BYTES_WRITTEN"]
291+
assert posix_bytes + stdio_bytes == 528
292+
except RuntimeError:
293+
print(f"Error: The file '{path_to_log}' was not found.")

0 commit comments

Comments
 (0)