1 /*
2  *  Copyright (C) 2017 Adam Nielsen <a.nielsen@shikadi.net>
3  *
4  *  Test code for mbuffer's tape-end detection option.
5  *
6  *  Use:
7  *    LD_PRELOAD=$PWD/tapetest.so ./mbuffer -b5 -v5 --tapeaware -o output-test < in
8  *
9  *  Only specify one filename beginning with the string "output" as this is the
10  *  file that will be intercepted, and there can only be one.  write() calls
11  *  are passed through unchanged until the number of calls given by
12  *  EARLY_END_BLOCK has been reached.  Then every second write() will fail with
13  *  ENOSPC until FINAL_END_BLOCK calls have succeeded in total.  Then write()
14  *  will return ENOSPC continually until the next call to open(), when the
15  *  call count is reset to zero and the process starts again.
16  *
17  *  This emulates the behaviour of an LTO tape, with the alternating write()
18  *  failures used by the Linux kernel to signal the imminent end of the tape.
19  *  mbuffer closes and reopens the device after a tape change, which this code
20  *  picks up in the open() call, resetting the counts to simulate the insertion
21  *  of a fresh tape.  This allows mbuffer's tape-end-detection code to be
22  *  tested without needing an actual tape drive.
23  *
24  *  This program is free software: you can redistribute it and/or modify
25  *  it under the terms of the GNU General Public License as published by
26  *  the Free Software Foundation, either version 3 of the License, or
27  *  (at your option) any later version.
28  *
29  *  This program is distributed in the hope that it will be useful,
30  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
31  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32  *  GNU General Public License for more details.
33  *
34  *  You should have received a copy of the GNU General Public License
35  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
36  */
37 
38 /* BUG: tapetest.c does not handle block sizes > device size */
39 
40 #include "mbconf.h"	// defines _GNU_SOURCE
41 
42 #ifndef __USE_GNU
43 #define __USE_GNU	// for RTLD_NEXT
44 #endif
45 #include <dlfcn.h>
46 #include <errno.h>
47 #include <stdarg.h>
48 #include <stdio.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #define EXPAND(A) A ## 1
53 #define ISEMPTY(A) EXPAND(A)
54 
55 #if !defined(LIBC_OPEN) || (ISEMPTY(LIBC_OPEN) == 1)
56 #error name of libc open() could not be determined - test cannot be performed
57 #endif
58 
59 #if !defined(LIBC_WRITE) || (ISEMPTY(LIBC_WRITE) == 1)
60 #error name of libc write() could not be determined - test cannot be performed
61 #endif
62 
63 /* Block number where we start signalling imminent end of tape */
64 #define EARLY_END_BLOCK 5
65 
66 /* Block number where we indicate we have reached the end of the tape */
67 #define FINAL_END_BLOCK 10
68 
69 typedef int (*open_func_t)(const char *path, int oflag, ...);
70 typedef ssize_t (*write_func_t)(int filedes, const void *buf, size_t nbyte);
71 
72 static int block = 0;     /* Current block */
73 static int toggle = 0;    /* Used to return ENOSPC for every other write() */
74 static int file = -1;     /* File handle we are intercepting */
75 static int opencount = 0; /* Number of calls made to open() */
76 static open_func_t orig_open = 0;
77 static write_func_t orig_write = 0;
78 
LIBC_OPEN(const char * path,int oflag,...)79 int LIBC_OPEN(const char *path, int oflag, ...)
80 {
81 	int fd;
82 	char newpath[256];
83 	va_list val;
84 	va_start(val,oflag);
85 	int mode = va_arg(val,int);
86 	va_end(val);
87 	if (0 == orig_open) {
88 		orig_open = (open_func_t)dlsym(RTLD_NEXT, "open");
89 	}
90 	if (strncmp(path, "output", 6) == 0) {
91 		printf("[INTERCEPT] open: %s", path);
92 		/* Opening a file that starts with "output", this is the one we will
93 		   intercept. */
94 
95 		/* Add .000, .001, etc. onto the end */
96 		sprintf(newpath, "%s.%03d", path, ++opencount);
97 		printf(", intercepted and writing as %s", newpath);
98 
99 		fd = orig_open(newpath, oflag, mode);
100 
101 		/* Remember this descriptor */
102 		file = fd;
103 
104 		/* Reset the block count to zero every time the file is opened, so that we
105 		   get another full "tape". */
106 		block = 0;
107 		toggle = 0;
108 	} else {
109 		fd = orig_open(path, oflag, mode);
110 	}
111 	printf("\n");
112 	return fd;
113 }
114 
115 
LIBC_WRITE(int filedes,const void * buf,size_t nbyte)116 ssize_t LIBC_WRITE(int filedes, const void *buf, size_t nbyte)
117 {
118 	if (0 == orig_write) {
119 		orig_write = (write_func_t)dlsym(RTLD_NEXT, "write");
120 	}
121 	if (filedes == file) {
122 		printf("[INTERCEPT] write(block %d): ", block);
123 		if (block >= FINAL_END_BLOCK) {
124 			printf("ENOSPC (final)\n");
125 			errno = ENOSPC;
126 			return -1;
127 		} else if (block >= EARLY_END_BLOCK) {
128 			toggle++;
129 			toggle &= 1;
130 			if (toggle) {
131 				printf("ENOSPC (early)\n");
132 				errno = ENOSPC;
133 				return -1;
134 			}
135 		}
136 		printf("OK\n");
137 		block++;
138 	}
139 	return orig_write(filedes, buf, nbyte);
140 }
141 
142