1 /*
2 * Copyright (c) 2013-2022 Douglas Gilbert.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * SPDX-License-Identifier: BSD-2-Clause
27 */
28
29 #include <iostream>
30 #include <vector>
31 #include <system_error>
32 #include <thread>
33 #include <mutex>
34 #include <chrono>
35
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <errno.h>
42 #include <ctype.h>
43 #include <sys/ioctl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include "sg_lib.h"
47 #include "sg_pt.h"
48
49 static const char * version_str = "1.06 20220425";
50 static const char * util_name = "sg_tst_context";
51
52 /* This is a test program for checking that file handles keep their
53 * context properly when sent (synchronous) SCSI pass-through commands.
54 * A disk device is assumed and even-numbered threads send TEST UNIT
55 * READY commands while odd-numbered threads send alternating START STOP
56 * UNIT commands (i.e. start then stop then start, etc). The point is to
57 * check the results to make sure that they don't get the other command's
58 * response. For example a START STOP UNIT command should not see a "not
59 * ready" sense key.
60 *
61 * This is C++ code with some things from C++11 (e.g. threads) and was
62 * only just able to compile (when some things were reverted) with gcc/g++
63 * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support
64 * was not available until g++ version 4.8.1 and that is found in Fedora
65 * 19 and Ubuntu 13.10 .
66 *
67 * The build uses various object files from the <sg3_utils>/lib directory
68 * which is assumed to be a sibling of this examples directory. Those
69 * object files in the lib directory can be built with:
70 * cd <sg3_utils> ; ./configure ; cd lib; make
71 * Then:
72 * cd ../testing
73 * make sg_tst_context
74 *
75 */
76
77 using namespace std;
78 using namespace std::chrono;
79
80 #define DEF_NUM_PER_THREAD 200
81 #define DEF_NUM_THREADS 2
82
83 #define EBUFF_SZ 256
84
85
86 static mutex count_mutex;
87 static mutex console_mutex;
88 static unsigned int even_notreadys;
89 static unsigned int odd_notreadys;
90 static unsigned int ebusy_count;
91 static int verbose;
92
93
94 static void
usage(void)95 usage(void)
96 {
97 printf("Usage: %s [-e] [-h] [-n <n_per_thr>] [-N] [-R] [-s]\n"
98 " [-t <num_thrs>] [-v] [-V] <disk_device>\n",
99 util_name);
100 printf(" where\n");
101 printf(" -e use O_EXCL on open (def: don't)\n");
102 printf(" -h print this usage message then exit\n");
103 printf(" -n <n_per_thr> number of loops per thread "
104 "(def: %d)\n", DEF_NUM_PER_THREAD);
105 printf(" -N use O_NONBLOCK on open (def: don't)\n");
106 printf(" -R make sure device in ready (started) "
107 "state after\n"
108 " test (do extra iteration if "
109 "necessary)\n");
110 printf(" -s share an open file handle (def: one "
111 "per thread)\n");
112 printf(" -t <num_thrs> number of threads (def: %d)\n",
113 DEF_NUM_THREADS);
114 printf(" -v increase verbosity\n");
115 printf(" -V print version number then exit\n\n");
116 printf("Test if file handles keep context through to their responses. "
117 "Sends\nTEST UNIT READY commands on even threads (origin 0) and "
118 "START STOP\nUNIT commands on odd threads. Expect NOT READY "
119 "sense keys only\nfrom the even threads (i.e from TUR)\n");
120 }
121
122 static int
pt_err(int res)123 pt_err(int res)
124 {
125 if (res < 0)
126 fprintf(stderr, " pass through OS error: %s\n", safe_strerror(-res));
127 else if (SCSI_PT_DO_BAD_PARAMS == res)
128 fprintf(stderr, " bad pass through setup\n");
129 else if (SCSI_PT_DO_TIMEOUT == res)
130 fprintf(stderr, " pass through timeout\n");
131 else
132 fprintf(stderr, " do_scsi_pt error=%d\n", res);
133 return (res < 0) ? res : -EPERM /* -1 */;
134 }
135
136 static int
pt_cat_no_good(int cat,struct sg_pt_base * ptp,const unsigned char * sbp)137 pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp)
138 {
139 int slen;
140 char b[256];
141 const int bl = (int)sizeof(b);
142 const char * cp = NULL;
143
144 b[0] = '\0';
145 switch (cat) {
146 case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
147 sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b);
148 cp = " scsi status: %s\n";
149 break;
150 case SCSI_PT_RESULT_SENSE:
151 slen = get_scsi_pt_sense_len(ptp);
152 sg_get_sense_str("", sbp, slen, 1, bl, b);
153 cp = "%s\n";
154 break;
155 case SCSI_PT_RESULT_TRANSPORT_ERR:
156 get_scsi_pt_transport_err_str(ptp, bl, b);
157 cp = " transport: %s\n";
158 break;
159 case SCSI_PT_RESULT_OS_ERR:
160 get_scsi_pt_os_err_str(ptp, bl, b);
161 cp = " os: %s\n";
162 break;
163 default:
164 cp = " unknown pt result category (%d)\n";
165 break;
166 }
167 if (cp) {
168 lock_guard<mutex> lg(console_mutex);
169
170 fprintf(stderr, cp, b);
171 }
172 return -EIO /* -5 */;
173 }
174
175 #define TUR_CMD_LEN 6
176 #define SSU_CMD_LEN 6
177 #define NOT_READY SG_LIB_CAT_NOT_READY
178
179 /* Returns 0 for good, 1024 for a sense key of NOT_READY, or a negative
180 * errno */
181 static int
do_tur(struct sg_pt_base * ptp,int id)182 do_tur(struct sg_pt_base * ptp, int id)
183 {
184 int slen, res, cat;
185 unsigned char turCmdBlk [TUR_CMD_LEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
186 unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
187
188 clear_scsi_pt_obj(ptp);
189 set_scsi_pt_cdb(ptp, turCmdBlk, sizeof(turCmdBlk));
190 set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
191 res = do_scsi_pt(ptp, -1, 20 /* secs timeout */, verbose);
192 if (res) {
193 {
194 lock_guard<mutex> lg(console_mutex);
195
196 fprintf(stderr, "TEST UNIT READY do_scsi_pt() submission error, "
197 "id=%d\n", id);
198 }
199 res = pt_err(res);
200 goto err;
201 }
202 cat = get_scsi_pt_result_category(ptp);
203 if (SCSI_PT_RESULT_GOOD != cat) {
204 slen = get_scsi_pt_sense_len(ptp);
205 if ((SCSI_PT_RESULT_SENSE == cat) &&
206 (NOT_READY == sg_err_category_sense(sense_buffer, slen))) {
207 res = 1024;
208 goto err;
209 }
210 {
211 lock_guard<mutex> lg(console_mutex);
212
213 fprintf(stderr, "TEST UNIT READY do_scsi_pt() category problem, "
214 "id=%d\n", id);
215 }
216 res = pt_cat_no_good(cat, ptp, sense_buffer);
217 goto err;
218 }
219 res = 0;
220 err:
221 return res;
222 }
223
224 /* Returns 0 for good, 1024 for a sense key of NOT_READY, or a negative
225 * errno */
226 static int
do_ssu(struct sg_pt_base * ptp,int id,bool start)227 do_ssu(struct sg_pt_base * ptp, int id, bool start)
228 {
229 int slen, res, cat;
230 unsigned char ssuCmdBlk [SSU_CMD_LEN] = {0x1b, 0x0, 0x0, 0x0, 0x0, 0x0};
231 unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
232
233 if (start)
234 ssuCmdBlk[4] |= 0x1;
235 clear_scsi_pt_obj(ptp);
236 set_scsi_pt_cdb(ptp, ssuCmdBlk, sizeof(ssuCmdBlk));
237 set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
238 res = do_scsi_pt(ptp, -1, 40 /* secs timeout */, verbose);
239 if (res) {
240 {
241 lock_guard<mutex> lg(console_mutex);
242
243 fprintf(stderr, "START STOP UNIT do_scsi_pt() submission error, "
244 "id=%d\n", id);
245 }
246 res = pt_err(res);
247 goto err;
248 }
249 cat = get_scsi_pt_result_category(ptp);
250 if (SCSI_PT_RESULT_GOOD != cat) {
251 slen = get_scsi_pt_sense_len(ptp);
252 if ((SCSI_PT_RESULT_SENSE == cat) &&
253 (NOT_READY == sg_err_category_sense(sense_buffer, slen))) {
254 res = 1024;
255 goto err;
256 }
257 {
258 lock_guard<mutex> lg(console_mutex);
259
260 fprintf(stderr, "START STOP UNIT do_scsi_pt() category problem, "
261 "id=%d\n", id);
262 }
263 res = pt_cat_no_good(cat, ptp, sense_buffer);
264 goto err;
265 }
266 res = 0;
267 err:
268 return res;
269 }
270
271 static void
work_thread(const char * dev_name,int id,int num,bool share,int pt_fd,int nonblock,int oexcl,bool ready_after)272 work_thread(const char * dev_name, int id, int num, bool share,
273 int pt_fd, int nonblock, int oexcl, bool ready_after)
274 {
275 bool started = true;
276 int k;
277 int res = 0;
278 unsigned int thr_even_notreadys = 0;
279 unsigned int thr_odd_notreadys = 0;
280 struct sg_pt_base * ptp = NULL;
281
282 {
283 lock_guard<mutex> lg(console_mutex);
284
285 cerr << "Enter work_thread id=" << id << " num=" << num << " share="
286 << share << endl;
287 }
288 if (! share) { /* ignore passed ptp, make this thread's own */
289 int oflags = O_RDWR;
290 unsigned int thr_ebusy_count = 0;
291
292 if (nonblock)
293 oflags |= O_NONBLOCK;
294 if (oexcl)
295 oflags |= O_EXCL;
296 while (((pt_fd = scsi_pt_open_flags(dev_name, oflags, verbose)) < 0)
297 && (-EBUSY == pt_fd)) {
298 ++thr_ebusy_count;
299 this_thread::yield(); // give other threads a chance
300 }
301 if (pt_fd < 0) {
302 char ebuff[EBUFF_SZ];
303
304 snprintf(ebuff, EBUFF_SZ, "work_thread id=%d: error opening: %s",
305 id, dev_name);
306 perror(ebuff);
307 return;
308 }
309 if (thr_ebusy_count) {
310 lock_guard<mutex> lg(count_mutex);
311
312 ebusy_count += thr_ebusy_count;
313 }
314 }
315 /* The instance of 'struct sg_pt_base' is local to this thread but the
316 * pt_fd it contains may be shared, depending on the 'share' boolean. */
317 ptp = construct_scsi_pt_obj_with_fd(pt_fd, verbose);
318 if (NULL == ptp) {
319 fprintf(stderr, "work_thread id=%d: "
320 "construct_scsi_pt_obj_with_fd() failed, memory?\n", id);
321 return;
322 }
323 for (k = 0; k < num; ++k) {
324 if (0 == (id % 2)) {
325 /* Even thread ids do TEST UNIT READYs */
326 res = do_tur(ptp, id);
327 if (1024 == res) {
328 ++thr_even_notreadys;
329 res = 0;
330 }
331 } else {
332 /* Odd thread ids do START STOP UNITs, alternating between
333 * starts and stops */
334 started = (0 == (k % 2));
335 res = do_ssu(ptp, id, started);
336 if (1024 == res) {
337 ++thr_odd_notreadys;
338 res = 0;
339 }
340 }
341 if (res)
342 break;
343 if (ready_after && (! started))
344 do_ssu(ptp, id, true);
345 }
346 if (ptp)
347 destruct_scsi_pt_obj(ptp);
348 if ((! share) && (pt_fd >= 0))
349 close(pt_fd);
350
351 {
352 lock_guard<mutex> lg(count_mutex);
353
354 even_notreadys += thr_even_notreadys;
355 odd_notreadys += thr_odd_notreadys;
356 }
357
358 {
359 lock_guard<mutex> lg(console_mutex);
360
361 if (k < num)
362 cerr << "thread id=" << id << " FAILed at iteration: " << k
363 << " [negated errno: " << res << " <"
364 << safe_strerror(-res) << ">]" << endl;
365 else
366 cerr << "thread id=" << id << " normal exit" << '\n';
367 }
368 }
369
370
371 int
main(int argc,char * argv[])372 main(int argc, char * argv[])
373 {
374 int k;
375 int pt_fd = -1;
376 int oexcl = 0;
377 int nonblock = 0;
378 int num_per_thread = DEF_NUM_PER_THREAD;
379 bool ready_after = false;
380 bool share = false;
381 int num_threads = DEF_NUM_THREADS;
382 char * dev_name = NULL;
383
384 for (k = 1; k < argc; ++k) {
385 if (0 == memcmp("-e", argv[k], 2))
386 ++oexcl;
387 else if (0 == memcmp("-h", argv[k], 2)) {
388 usage();
389 return 0;
390 } else if (0 == memcmp("-n", argv[k], 2)) {
391 ++k;
392 if ((k < argc) && isdigit(*argv[k])) {
393 num_per_thread = sg_get_num(argv[k]);
394 if (num_per_thread<= 0) {
395 fprintf(stderr, "want positive integer for number "
396 "per thread\n");
397 return 1;
398 }
399 } else
400 break;
401 } else if (0 == memcmp("-N", argv[k], 2))
402 ++nonblock;
403 else if (0 == memcmp("-R", argv[k], 2))
404 ready_after = true;
405 else if (0 == memcmp("-s", argv[k], 2))
406 share = true;
407 else if (0 == memcmp("-t", argv[k], 2)) {
408 ++k;
409 if ((k < argc) && isdigit(*argv[k]))
410 num_threads = atoi(argv[k]);
411 else
412 break;
413 } else if (0 == memcmp("-v", argv[k], 2))
414 ++verbose;
415 else if (0 == memcmp("-V", argv[k], 2)) {
416 printf("%s version: %s\n", util_name, version_str);
417 return 0;
418 } else if (*argv[k] == '-') {
419 printf("Unrecognized switch: %s\n", argv[k]);
420 dev_name = NULL;
421 break;
422 }
423 else if (! dev_name)
424 dev_name = argv[k];
425 else {
426 printf("too many arguments\n");
427 dev_name = 0;
428 break;
429 }
430 }
431 if (0 == dev_name) {
432 usage();
433 return 1;
434 }
435 try {
436 if (share) {
437 int oflags = O_RDWR;
438
439 if (nonblock)
440 oflags |= O_NONBLOCK;
441 if (oexcl)
442 oflags |= O_EXCL;
443 while (((pt_fd = scsi_pt_open_flags(dev_name, oflags, verbose))
444 < 0) && (-EBUSY == pt_fd)) {
445 ++ebusy_count;
446 sleep(0); // process yield ??
447 }
448 if (pt_fd < 0) {
449 char ebuff[EBUFF_SZ];
450
451 snprintf(ebuff, EBUFF_SZ, "main: error opening: %s",
452 dev_name);
453 perror(ebuff);
454 return 1;
455 }
456 /* Tried calling construct_scsi_pt_obj_with_fd() here but that
457 * doesn't work since 'struct sg_pt_base' objects aren't
458 * thread-safe without user space intervention (e.g. mutexes). */
459 }
460
461 vector<thread *> vt;
462
463 for (k = 0; k < num_threads; ++k) {
464 thread * tp = new thread {work_thread, dev_name, k,
465 num_per_thread, share, pt_fd, nonblock,
466 oexcl, ready_after};
467 vt.push_back(tp);
468 }
469
470 for (k = 0; k < (int)vt.size(); ++k)
471 vt[k]->join();
472
473 for (k = 0; k < (int)vt.size(); ++k)
474 delete vt[k];
475
476 if (share)
477 scsi_pt_close_device(pt_fd);
478
479 cout << "Expected not_readys on TEST UNIT READY: " << even_notreadys
480 << endl;
481 cout << "UNEXPECTED not_readys on START STOP UNIT: "
482 << odd_notreadys << endl;
483 if (ebusy_count)
484 cout << "Number of EBUSYs (on open): " << ebusy_count << endl;
485
486 }
487 catch(system_error& e) {
488 cerr << "got a system_error exception: " << e.what() << '\n';
489 auto ec = e.code();
490 cerr << "category: " << ec.category().name() << '\n';
491 cerr << "value: " << ec.value() << '\n';
492 cerr << "message: " << ec.message() << '\n';
493 cerr << "\nNote: if g++ may need '-pthread' or similar in "
494 "compile/link line" << '\n';
495 }
496 catch(...) {
497 cerr << "got another exception: " << '\n';
498 }
499 if (pt_fd >= 0)
500 close(pt_fd);
501 return 0;
502 }
503