xref: /aosp_15_r20/external/sg3_utils/testing/sg_tst_context.cpp (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
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