Bug Summary

File:.build-ci/../plugins/samsung/samsung-nvme.c
Warning:line 662, column 10
Potential leak of memory pointed to by 'data'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name samsung-nvme.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model static -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -fcoverage-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -resource-dir /usr/bin/../lib/clang/22 -include /__w/nvme-cli/nvme-cli/.build-ci/nvme-config.h -I nvme.p -I . -I .. -I src -I ../src -I ccan -I ../ccan -I libnvme/src -I ../libnvme/src -I /usr/include/json-c -D _FILE_OFFSET_BITS=64 -D _GNU_SOURCE -U NDEBUG -internal-isystem /usr/bin/../lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/16/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-opt-analyze-headers -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /__w/nvme-cli/nvme-cli/.build-ci/scan-results/2026-09-23-073103-589-1 -x c ../plugins/samsung/samsung-nvme.c
1// SPDX-License-Identifier: GPL-2.0-or-later
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <unistd.h>
6#include <fcntl.h>
7#include <sys/stat.h>
8#include <sys/time.h>
9#include <errno(*__errno_location ()).h>
10#include <dirent.h>
11
12#include <libnvme.h>
13
14#include <ccan/endian/endian.h>
15#include <shared/compiler-attributes-util.h>
16#include <shared/fs-util.h>
17#include <shared/io-util.h>
18#include <shared/proc-util.h>
19#include <shared/progress-util.h>
20#include <shared/string-util.h>
21
22#include "cleanup.h"
23#include "global-ctx.h"
24#include "nvme-cmds.h"
25#include "nvme-print.h"
26#include "plugin.h"
27
28#define SAMSUNG_PLUGIN_VERSION"3.0.15" "3.0.15"
29
30#define SAMSUNG_GENERAL_FILE_WRITE_ERROR-1 -1
31#define SAMSUNG_GENERAL_FILE_OPEN_ERROR-2 -2
32#define SAMSUNG_GENERAL_INVALID_PARAMETER_ERROR-3 -3
33#define SAMSUNG_GENERAL_INVALID_VID_ERROR-4 -4
34#define SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5 -5
35
36static bool_Bool g_hide_progress;
37
38static const char *samsung_plugin_status_to_string(__s32 status)
39{
40 const char *str;
41
42 switch (status) {
43 case SAMSUNG_GENERAL_FILE_WRITE_ERROR-1:
44 str = "File write error.";
45 break;
46 case SAMSUNG_GENERAL_FILE_OPEN_ERROR-2:
47 str = "File open error.";
48 break;
49 case SAMSUNG_GENERAL_INVALID_PARAMETER_ERROR-3:
50 str = "Invalid parameter error.";
51 break;
52 case SAMSUNG_GENERAL_INVALID_VID_ERROR-4:
53 str = "Only Samsung products are supported.";
54 break;
55 case SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5:
56 str = "Memory allocation error.";
57 break;
58 default:
59 str = "Unknown.";
60 }
61 return str;
62}
63
64static void samsung_print_error(int err)
65{
66 if (err > 0) {
67 if ((err & 0xFF) == NVME_SC_INVALID_FIELD
68 || (err & 0xFF) == NVME_SC_INVALID_LOG_PAGE) {
69 fprintf(stderrstderr, "This device is not supported.\n");
70 } else {
71 fprintf(stderrstderr, "NVMe Status: %s(0x%X)\n",
72 libnvme_status_to_string(err, false0), err);
73 }
74 } else if (err < 0)
75 fprintf(stderrstderr, "%s(%d)\n", samsung_plugin_status_to_string(err), err);
76}
77
78static void samsung_initialize(void)
79{
80 // Set stdout to unbuffered (always fflush immediately)
81 // Must be called before any other printf().
82 setvbuf(stdoutstdout, NULL((void*)0), _IONBF2, 0);
83}
84
85static int samsung_nvme_submit_admin_passthru(
86 struct libnvme_transport_handle *hdl, struct libnvme_passthru_cmd *cmd)
87{
88 return libnvme_exec_admin_passthru(hdl, cmd);
89}
90
91static int samsung_nvme_get_log_page(struct libnvme_transport_handle *hdl,
92 __u32 nsid, __u8 log_id, __u32 data_len, void *data, __u64 lpo,
93 __u8 lsp, __u8 rae, __u32 cdw14)
94{
95 struct libnvme_passthru_cmd cmd = {
96 .opcode = nvme_admin_get_log_page,
97 .nsid = nsid,
98 .addr = (__u64)(uintptr_t) data,
99 .data_len = data_len,
100 .cdw10 = ((lsp & 0x7F) << 8) | log_id,
101 .cdw12 = lpo & 0xFFFFFFFF,
102 .cdw13 = lpo >> 32,
103 .cdw14 = cdw14,
104 };
105
106 return libnvme_get_log(hdl, &cmd, rae, data_len);
107}
108
109#define UNIT_DATA_SIZE_1KB(1024) (1024)
110#define UNIT_DATA_SIZE_5KB(5 * 1024) (5 * 1024)
111#define UNIT_DATA_SIZE_8KB(8 * 1024) (8 * 1024)
112#define UNIT_DATA_SIZE_16KB(16 * 1024) (16 * 1024)
113#define UNIT_DATA_SIZE_32KB(32 * 1024) (32 * 1024)
114#define UNIT_DATA_SIZE_78KB(78 * 1024) (78 * 1024)
115#define UNIT_DATA_SIZE_94KB(94 * 1024) (94 * 1024)
116#define UNIT_DATA_SIZE_127KB(127 * 1024) (127 * 1024)
117#define UNIT_DATA_SIZE_128KB(128 * 1024) (128 * 1024)
118#define UNIT_DATA_SIZE_512KB(512 * 1024) (512 * 1024)
119
120/*
121 * The serial number is space padded rather than NUL terminated, so copy
122 * the whole field and trim the padding. sn holds sizeof(ctrl->sn) + 1
123 * bytes. Trimming from the end keeps a serial that has a space in it.
124 *
125 * The drive supplies this field and make_file_path() puts it straight
126 * into the dump file names, so sanitize it here: a serial holding "../"
127 * would otherwise write the dumps outside the directory -O asked for.
128 */
129static void get_serial_number(struct nvme_id_ctrl *ctrl, char *sn)
130{
131 memcpy(sn, ctrl->sn, sizeof(ctrl->sn));
132 sn[sizeof(ctrl->sn)] = '\0';
133 shr_sanitize_name(shr_rtrim(sn));
134}
135
136/*
137 * The name of one dump file: the -O prefix, if any, with the serial number
138 * and the feature name appended.
139 * Return: an allocated path the caller frees, or NULL when out of memory.
140 */
141static char *make_file_path(const char *feature_name, const char *file_name,
142 const char *sn)
143{
144 char *path = NULL((void*)0);
145
146 if (asprintf(&path, "%s%s_%s.bin", file_name ? file_name : "", sn,
147 feature_name) < 0)
148 return NULL((void*)0);
149
150 return path;
151}
152
153static void measure_time(struct timeval *begin, bool_Bool set_begin)
154{
155 if (set_begin)
156 gettimeofday(begin, NULL((void*)0));
157 else {
158 struct timeval end;
159
160 gettimeofday(&end, NULL((void*)0));
161 if (end.tv_usec < begin->tv_usec)
162 printf("Time Taken: %ld.%.6lds\n",
163 (long)(end.tv_sec - begin->tv_sec - 1),
164 (long)(1000000 + end.tv_usec - begin->tv_usec));
165 else
166 printf("Time Taken: %ld.%.6lds\n",
167 (long)(end.tv_sec - begin->tv_sec),
168 (long)(end.tv_usec - begin->tv_usec));
169 }
170}
171
172/*
173 * __s64/__u64 are long on LP64 targets such as ppc64le, so cast in the
174 * macro to keep the %lld conversions below correct everywhere.
175 */
176#define SEC(time)((long long)((time) / 1000000)) ((long long)((time) / 1000000))
177#define USEC_3(time)((long long)((time) % 1000000 / 1000)) ((long long)((time) % 1000000 / 1000))
178
179static void measure_loop_time(struct timeval *begin, __s64 loop_cnt,
180 __s64 total_loop_cnt)
181{
182 static __s64 time_10;
183 static __s64 time_10_per_loop;
184 static __s64 time_100;
185 static __s64 time_100_per_loop;
186 static __s64 time_1000;
187 static __s64 time_1000_per_loop;
188 struct timeval end;
189
190 /*
191 * Developer timing, on its own axis: this table answers to --verbose
192 * only, never to --hide-progress.
193 */
194 if (!nvme_args.verbose)
195 return;
196
197 if (loop_cnt == 9) {
198 gettimeofday(&end, NULL((void*)0));
199 time_10 = (end.tv_sec * 1000000 + end.tv_usec)
200 - (begin->tv_sec * 1000000 + begin->tv_usec);
201 time_10_per_loop = time_10 / 10;
202 } else if (loop_cnt == 99) {
203 gettimeofday(&end, NULL((void*)0));
204 time_100 = (end.tv_sec * 1000000 + end.tv_usec)
205 - (begin->tv_sec * 1000000 + begin->tv_usec);
206 time_100_per_loop = time_100 / 100;
207 } else if (loop_cnt == 999) {
208 gettimeofday(&end, NULL((void*)0));
209 time_1000 = (end.tv_sec * 1000000 + end.tv_usec)
210 - (begin->tv_sec * 1000000 + begin->tv_usec);
211 time_1000_per_loop = time_1000 / 1000;
212 }
213
214 if (loop_cnt + 1 == total_loop_cnt) {
215 printf("Total loops: %lld loops\n", (long long)total_loop_cnt);
216 printf("Loops | Elapsed time | Avg time per loop\n");
217
218 if (total_loop_cnt >= 10) {
219 printf(" 10 | %7lld.%03lld | %13lld.%03lld\n",
220 SEC(time_10)((long long)((time_10) / 1000000)), USEC_3(time_10)((long long)((time_10) % 1000000 / 1000)),
221 SEC(time_10_per_loop)((long long)((time_10_per_loop) / 1000000)), USEC_3(time_10_per_loop)((long long)((time_10_per_loop) % 1000000 / 1000)));
222 }
223 if (total_loop_cnt >= 100) {
224 printf(" 100 | %7lld.%03lld | %13lld.%03lld\n",
225 SEC(time_100)((long long)((time_100) / 1000000)), USEC_3(time_100)((long long)((time_100) % 1000000 / 1000)),
226 SEC(time_100_per_loop)((long long)((time_100_per_loop) / 1000000)), USEC_3(time_100_per_loop)((long long)((time_100_per_loop) % 1000000 / 1000)));
227 }
228 if (total_loop_cnt >= 1000) {
229 printf(" 1000 | %7lld.%03lld | %13lld.%03lld\n",
230 SEC(time_1000)((long long)((time_1000) / 1000000)), USEC_3(time_1000)((long long)((time_1000) % 1000000 / 1000)),
231 SEC(time_1000_per_loop)((long long)((time_1000_per_loop) / 1000000)), USEC_3(time_1000_per_loop)((long long)((time_1000_per_loop) % 1000000 / 1000)));
232 }
233
234 gettimeofday(&end, NULL((void*)0));
235 __s64 total_time = (end.tv_sec * 1000000 + end.tv_usec)
236 - (begin->tv_sec * 1000000 + begin->tv_usec);
237 __s64 time_per_loop = total_time / total_loop_cnt;
238
239 printf("%5lld | %7lld.%03lld | %13lld.%03lld\n",
240 (long long)total_loop_cnt,
241 SEC(total_time)((long long)((total_time) / 1000000)), USEC_3(total_time)((long long)((total_time) % 1000000 / 1000)),
242 SEC(time_per_loop)((long long)((time_per_loop) / 1000000)), USEC_3(time_per_loop)((long long)((time_per_loop) % 1000000 / 1000)));
243 }
244}
245
246/*
247 * Insert dir_name as an extra directory level in front of the final
248 * component of base_dir, create that directory, and hand back both paths:
249 *
250 * base_dir dir_name *dir_path *result
251 * (NULL) path2 ./path2 ./path2/
252 * name path2 ./path2 ./path2/name
253 * /path1/ path2 /path1/path2 /path1/path2/
254 * /path1/name path2 /path1/path2 /path1/path2/name
255 *
256 * Both output strings are allocated and the caller frees them.
257 * Creating dir_name fails if anything already sits at that path.
258 */
259static int insert_dir(const char *base_dir, const char *dir_name,
260 char **dir_path, char **result)
261{
262 __cleanup_free__attribute__((cleanup(shr_freep))) char *dir = NULL((void*)0);
263 __cleanup_free__attribute__((cleanup(shr_freep))) char *path = NULL((void*)0);
264 const char *prefix = "./";
265 const char *name = "";
266 int prefix_len = 2;
267 int ret;
268
269 if (base_dir) {
270 name = shr_basename(base_dir);
271 if (name != base_dir) {
272 prefix = base_dir;
273 prefix_len = (int)shr_dir_prefix_len(base_dir);
274 }
275 }
276
277 if (asprintf(&dir, "%.*s%s", prefix_len, prefix, dir_name) < 0)
278 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
279
280 if (asprintf(&path, "%s/%s", dir, name) < 0)
281 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
282
283 /*
284 * Every dump sits here until it is archived, so keep the staging
285 * directory to this user. shr_mkdir() rather than shr_mkdir_p():
286 * a path that is already taken must fail rather than be reused,
287 * since it may be a symlink or a directory somebody else can
288 * write to. Its parent is already there either way, created by
289 * the shr_mkdir_from_fname() the caller runs first.
290 */
291 ret = shr_mkdir(dir, 0700);
292 if (ret < 0) {
293 fprintf(stderrstderr, "mkdir %s: %s\n", dir, strerror(-ret));
294 if (ret == -EEXIST17)
295 fprintf(stderrstderr,
296 "A run that was interrupted leaves it behind. "
297 "Remove %s and run the command again.\n", dir);
298 return SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
299 }
300
301 *dir_path = dir;
302 dir = NULL((void*)0);
303 *result = path;
304 path = NULL((void*)0);
305
306 return 0;
307}
308
309/*
310 * Run argv without a shell. No command string is built, so a serial number
311 * or an output path can never become syntax. shr_spawnp() resolves argv[0]
312 * through PATH, as the micron plugin does: tar and rm sit in different
313 * directories across platforms, so there is no absolute path to prefer.
314 */
315static int run_command(const char *const argv[])
316{
317 shr_proc_t proc;
318 bool_Bool exited;
319 int code;
320 int ret;
321
322 /*
323 * The child writes to this process's stdout, so flush what is still
324 * buffered here or it lands after the child's output.
325 */
326 fflush(stdoutstdout);
327
328 ret = shr_spawnp(argv, -1, -1, &proc);
329 if (!ret)
330 ret = shr_wait_proc(proc, &exited, &code);
331 if (ret) {
332 fprintf(stderrstderr, "%s: %s\n", argv[0], strerror(-ret));
333 return SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
334 }
335
336 if (!exited) {
337 fprintf(stderrstderr, "%s was killed.\n", argv[0]);
338 return SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
339 }
340
341 if (code != 0) {
342 fprintf(stderrstderr, "%s exited with status %d.\n", argv[0], code);
343 return SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
344 }
345
346 return 0;
347}
348
349/* The names of the dump files staged for archiving. */
350struct dump_names {
351 char **name;
352 size_t count;
353};
354
355static void free_dump_names(struct dump_names *names)
356{
357 size_t i;
358
359 for (i = 0; i < names->count; i++)
360 free(names->name[i]);
361 free(names->name);
362 names->name = NULL((void*)0);
363 names->count = 0;
364}
365
366/*
367 * Collect the staged file names so that tar can be given them one by one.
368 * A "." operand would be shorter, but it also archives the staging
369 * directory itself as a "./" member carrying that directory's mode, and
370 * extracting the member applies the mode to whatever directory the archive
371 * is unpacked into. Skipping names that start with '.' keeps the
372 * behaviour of the shell glob this replaces.
373 */
374static int list_dump_names(const char *dir, struct dump_names *names)
375{
376 struct dirent *entry;
377 DIR *d;
378 int ret = 0;
379
380 names->name = NULL((void*)0);
381 names->count = 0;
382
383 d = opendir(dir);
384 if (!d) {
385 fprintf(stderrstderr, "opendir %s: %s\n", dir, strerror(errno(*__errno_location ())));
386 return SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
387 }
388
389 while ((entry = readdir(d))) {
390 char **grown;
391
392 if (entry->d_name[0] == '.')
393 continue;
394
395 grown = realloc(names->name,
396 (names->count + 1) * sizeof(*grown));
397 if (!grown) {
398 ret = SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
399 break;
400 }
401 names->name = grown;
402
403 names->name[names->count] = strdup(entry->d_name);
404 if (!names->name[names->count]) {
405 ret = SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
406 break;
407 }
408 names->count++;
409 }
410
411 closedir(d);
412
413 if (ret != 0)
414 free_dump_names(names);
415 else if (names->count == 0) {
416 fprintf(stderrstderr, "No dump was collected in %s.\n", dir);
417 ret = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
418 }
419
420 return ret;
421}
422
423/* "tar" "cvzf" <archive> "-C" <dir> "--", ahead of the file names */
424#define TAR_ARGC_FIXED6 6
425
426static int compress_dump_files(const char *temp_dir, const char *output_prefix,
427 const char *sn)
428{
429 __cleanup_free__attribute__((cleanup(shr_freep))) char *targz_file = NULL((void*)0);
430 __cleanup_free__attribute__((cleanup(shr_freep))) const char **argv = NULL((void*)0);
431 const char *prefix = output_prefix ? output_prefix : "./";
432 const char *archive_name = shr_basename(prefix);
433 const char *local_prefix = "";
434 struct dump_names names = { NULL((void*)0), 0 };
435 size_t argc;
436 size_t i;
437 int ret;
438
439#if !defined(_WIN32)
440 /*
441 * Keep a colon before the first slash from invoking tar's remote mode.
442 */
443 if (prefix[strcspn(prefix, "/:")] == ':')
444 local_prefix = "./";
445#endif
446
447 if (asprintf(&targz_file, "%s%sSamsung_Dump_%s.tar.gz",
448 local_prefix, prefix, sn) < 0)
449 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
450
451 ret = list_dump_names(temp_dir, &names);
452 if (ret != 0)
453 return ret;
454
455 argv = calloc(TAR_ARGC_FIXED6 + names.count + 1, sizeof(*argv));
456 if (!argv) {
457 free_dump_names(&names);
458 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
459 }
460
461 argc = 0;
462 argv[argc++] = "tar";
463 argv[argc++] = "cvzf";
464 argv[argc++] = targz_file;
465 argv[argc++] = "-C";
466 argv[argc++] = temp_dir;
467 argv[argc++] = "--";
468 for (i = 0; i < names.count; i++)
469 argv[argc++] = names.name[i];
470 argv[argc] = NULL((void*)0);
471
472 printf("Compressing...\n");
473 ret = run_command(argv);
474 free_dump_names(&names);
475 if (ret != 0)
476 return ret;
477
478 printf("%sSamsung_Dump_%s.tar.gz saved at the designated location.\n",
479 archive_name, sn);
480
481 return run_command((const char *const []) {
482 "rm", "-rf", "--", temp_dir, NULL((void*)0)
483 });
484}
485
486static void print_border(const char *dump_name, char cmd_type, int arg1, int arg2)
487{
488 printf("\n-------------------------------------------------------------\n");
489 printf("\nExtracting %s", dump_name);
490 if (cmd_type == 'g')
491 printf("([Get Log] LID %Xh)\n", arg1);
492 else if (cmd_type == 's')
493 printf("([Secu] SPSP %Xh Op %Xh)\n", arg1, arg2);
494 else if (cmd_type == 'v')
495 printf("([Vendor] Op %Xh)\n", arg1);
496}
497
498
499/*
500 * The HOST/CTLR values double as the Get Log Page LID (0x07 / 0x08), so keep
501 * them fixed. HOST_0 / HOST_1 select the Telemetry Host-Initiated LSP
502 * (0 = retain the existing capture, 1 = generate a new one).
503 */
504enum DUMP_TYPE {
505 DUMP_TYPE_ALL = 0,
506 DUMP_TYPE_HOST = 7,
507 DUMP_TYPE_CTLR = 8,
508 DUMP_TYPE_HOST_0,
509 DUMP_TYPE_HOST_1,
510 DUMP_TYPE_VENDOR,
511 DUMP_TYPE_MAX,
512};
513
514#define TELEMETRY_HEADER_SIZE512 512
515#define TELEMETRY_BYTE_PER_BLOCK512 512
516
517#pragma pack(push, 1)
518
519struct reason_identifier {
520 __u8 error_id[64]; //[ 63: 0]
521 __u8 file_id[8]; //[ 71:64]
522 __u8 line_number[2]; //[ 73:72]
523 __u8 valid_flags; //[ 74:74]
524 __u8 rsvd75[21]; //[ 95:75]
525 __u8 vu_reason_extension[32]; //[127:96]
526};
527
528struct telemetry_initiated_log {
529 __u8 log_identifier; //[ 0: 0]
530 __u8 rsvd1[4]; //[ 4: 1]
531 __u8 IEEE[3]; //[ 7: 5]
532 __le16 data_area1_last_block; //[ 9: 8]
533 __le16 data_area2_last_block; //[ 11: 10]
534 __le16 data_area3_last_block; //[ 13: 12]
535 __u8 rsvd14[2]; //[ 15: 14]
536 __le32 data_area4_last_block; //[ 19: 16]
537 __u8 rsvd20[360]; //[379: 20]
538 __u8 _380; //[380:380]
539 __u8 _381; //[381:381]
540 __u8 ctlr_init_data_available; //[382:382]
541 __u8 ctlr_init_data_gen_number; //[383:383]
542 struct reason_identifier ri; //[511:384]
543};
544
545#pragma pack(pop)
546
547static void print_telemetry_header(struct telemetry_initiated_log *log_header,
548 int tele_type)
549{
550 if (log_header != NULL((void*)0)) {
551 unsigned int i = 0, j = 0;
552
553 if (tele_type == DUMP_TYPE_HOST)
554 printf("============== Telemetry Host Header ==============\n");
555 else
556 printf("=========== Telemetry Controller Header ===========\n");
557
558 printf("Log Identifier : 0x%02X\n", log_header->log_identifier);
559 printf("IEEE : 0x%02X%02X%02X\n",
560 log_header->IEEE[0], log_header->IEEE[1], log_header->IEEE[2]);
561 printf("Data Area 1 Last Block : 0x%04X\n",
562 le16_to_cpu(log_header->data_area1_last_block));
563 printf("Data Area 2 Last Block : 0x%04X\n",
564 le16_to_cpu(log_header->data_area2_last_block));
565 printf("Data Area 3 Last Block : 0x%04X\n",
566 le16_to_cpu(log_header->data_area3_last_block));
567 printf("Data Area 4 Last Block : 0x%08X\n",
568 le32_to_cpu(log_header->data_area4_last_block));
569
570 if (tele_type == DUMP_TYPE_HOST) {
571 printf("Host-init Scope : 0x%02X\n",
572 log_header->_380);
573 printf("Host-init Data Generation Number : 0x%02X\n",
574 log_header->_381);
575 } else
576 printf("Ctlr-init Scope : 0x%02X\n",
577 log_header->_381);
578
579 printf("Ctlr-init Data Available : 0x%02X\n",
580 log_header->ctlr_init_data_available);
581 printf("Ctlr-init Data Generation Number : 0x%02X\n",
582 log_header->ctlr_init_data_gen_number);
583
584 printf("\n<Reason Identifier>\n");
585
586 __u8 *ri = (__u8 *)&log_header->ri;
587
588 for (i = 0; i < 8; i++) {
589 printf(" ");
590 for (j = 0; j < 16; j++)
591 printf(" %02X", ri[127 - ((i * 16) + j)]);
592 printf("\n");
593 }
594
595 printf("===================================================\n");
596 }
597}
598
599// Get DA4S bit (Data Area 4 Support)
600// Admin command - Identify - Identify Controller - Log Page Attributes
601static int get_da4s(struct nvme_id_ctrl *ctrl, bool_Bool *da4s)
602{
603 *da4s = ((ctrl->lpa >> 6) & 0x01);
604 return 0;
605}
606
607// Get MCDAS bit (Maximum Created Data Area Support)
608// Admin command - Get Log Page - Supported Log Pages - LID Supported and Effects
609static int get_mcdas(struct libnvme_transport_handle *hdl, __u32 nsid, bool_Bool *mcdas)
610{
611 __u8 data[1024] = {0,};
612 int err = 0;
613
614 err = samsung_nvme_get_log_page(hdl, nsid, 0, sizeof(data), data, 0, 0, 0, 0);
615 if (err != 0) {
616 *mcdas = false0;
617 return err;
618 }
619
620 // data[30] is "lower 8 bits of LIDSP" for "LID Supported and Effects Data
621 // Structure for LID 7"; data[30] & 1 is MCDAS for LID 7.
622 *mcdas = (data[30] & 1) ? true1 : false0;
623
624 return 0;
625}
626
627static int get_telemetry_header(struct libnvme_transport_handle *hdl, __u32 nsid,
628 __u8 tele_type, __u32 data_len, void *data, __u8 lsp, __u8 rae)
629{
630 return samsung_nvme_get_log_page(hdl, nsid, tele_type, data_len, data, 0,
631 lsp, rae, 0);
632}
633
634/*
635 * dump_size is 64-bit: Data Area 4 carries a 32-bit last block number, so a
636 * telemetry area can be larger than INT_MAX even though each individual
637 * transfer stays within transfer_size.
638 */
639static int extract_telemetry_dump_data(char *feature_name, char *file_name,
640 char *sn, __s64 dump_size, int transfer_size,
641 struct libnvme_transport_handle *hdl, __u32 nsid, __u8 log_id,
642 __u8 lsp, __u64 offset, bool_Bool rae)
643{
644 __cleanup_free__attribute__((cleanup(shr_freep))) char *feature_name_header = NULL((void*)0);
645 __cleanup_free__attribute__((cleanup(shr_freep))) char *file_path_header = NULL((void*)0);
646 __cleanup_free__attribute__((cleanup(shr_freep))) char *file_path = NULL((void*)0);
647 __cleanup_free__attribute__((cleanup(shr_freep))) char *data = NULL((void*)0);
648 __s64 loop = 0;
649 int err = 0;
650
651 struct timeval begin;
652 int output = -1, output_header = -1;
653 __s64 total_loop_cnt = dump_size / transfer_size;
654 int last_xfer_size = dump_size % transfer_size;
655 int retrieve_size;
656
657 data = calloc(transfer_size, sizeof(char));
1
Memory is allocated
658 if (!data)
2
Assuming 'data' is non-null
3
Taking false branch
659 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
660
661 if (asprintf(&feature_name_header, "%s_header", feature_name) < 0)
4
Assuming the condition is true
5
Taking true branch
662 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
6
Potential leak of memory pointed to by 'data'
663
664 file_path = make_file_path(feature_name, file_name, sn);
665 file_path_header = make_file_path(feature_name_header, file_name, sn);
666 if (!file_path || !file_path_header)
667 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
668
669 if (last_xfer_size != 0)
670 total_loop_cnt++;
671 else
672 last_xfer_size = transfer_size;
673
674 measure_time(&begin, true1);
675
676 err = get_telemetry_header(hdl, nsid, log_id, TELEMETRY_HEADER_SIZE512, data,
677 lsp, rae);
678 if (err != 0)
679 goto end;
680
681 output_header = shr_open_rawdata(file_path_header, O_WRONLY | O_CREAT | O_TRUNC, 0666)open((file_path_header), (01 | 0100 | 01000), 0666);
682 if (output_header < 0) {
683 err = SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
684 goto end;
685 }
686
687 if (shr_write_all(output_header, data, TELEMETRY_HEADER_SIZE512)) {
688 err = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
689 goto close_output_header;
690 }
691
692 for (loop = 0; loop < total_loop_cnt; loop++) {
693 retrieve_size = (loop == total_loop_cnt - 1) ? last_xfer_size : transfer_size;
694 memset(data, 0, transfer_size);
695
696 err = samsung_nvme_get_log_page(hdl, nsid, log_id, retrieve_size, data,
697 offset, lsp, rae, 0);
698 if (err != 0) {
699 if (loop > 0)
700 goto close_output;
701 else
702 goto close_output_header;
703 }
704
705 if (loop == 0) {
706 output = shr_open_rawdata(file_path,open((file_path), (01 | 0100 | 01000), 0666)
707 O_WRONLY | O_CREAT | O_TRUNC, 0666)open((file_path), (01 | 0100 | 01000), 0666);
708 if (output < 0) {
709 err = SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
710 goto close_output_header;
711 }
712 }
713
714 if (shr_write_all(output, data, retrieve_size)
715 || shr_write_all(output_header, data, retrieve_size)) {
716 err = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
717 goto close_output;
718 }
719
720 measure_loop_time(&begin, loop, total_loop_cnt);
721 offset += retrieve_size;
722 if (!g_hide_progress)
723 shr_spinner(feature_name,
724 (loop + 1) / (float)total_loop_cnt, stdoutstdout);
725 }
726
727 /* Completion line: printed even under --hide-progress, see above. */
728 shr_spinner(feature_name, 1.0, stdoutstdout);
729 printf("\n");
730 printf("The log file was saved in \"%s\"\n", file_path);
731 printf("The log file was saved in \"%s\"\n", file_path_header);
732 measure_time(&begin, false0);
733
734close_output:
735 if (output >= 0)
736 close(output);
737
738close_output_header:
739 if (output_header >= 0)
740 close(output_header);
741
742end:
743 return err;
744}
745
746static int open_write_close(char *file_name, __u8 *buffer, int size)
747{
748 int err = 0;
749 int output = 0;
750
751 output = shr_open_rawdata(file_name, O_WRONLY | O_CREAT | O_TRUNC, 0666)open((file_name), (01 | 0100 | 01000), 0666);
752 if (output < 0) {
753 err = SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
754 return err;
755 }
756
757 if (shr_write_all(output, buffer, size))
758 err = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
759
760 close(output);
761 return err;
762}
763
764static int get_telemetry_dump(struct libnvme_transport_handle *hdl,
765 struct nvme_id_ctrl *ctrl, char *file_name, char *sn,
766 enum DUMP_TYPE tele_type, int data_area, bool_Bool header_print,
767 int transfer_size)
768{
769 __u32 nsid = NVME_NSID_ALL;
770 int err = 0;
771 __u8 lsp = 0, rae = 1;
772 char *feature_name = 0;
773
774 bool_Bool is_enabled_data_area4 = false0;
775 bool_Bool da4s = false0;
776 bool_Bool mcdas = false0;
777 bool_Bool host_behavior_changed = false0;
778
779 if (tele_type == DUMP_TYPE_HOST_0) {
780 feature_name = "Host(0)";
781 lsp = 0;
782 tele_type = DUMP_TYPE_HOST;
783 } else if (tele_type == DUMP_TYPE_HOST_1) {
784 feature_name = "Host(1)";
785 lsp = 1;
786 tele_type = DUMP_TYPE_HOST;
787 } else { // DUMP_TYPE_CTLR
788 feature_name = "Controller";
789 lsp = 0;
790 }
791
792 struct telemetry_initiated_log log_header;
793 __cleanup_free__attribute__((cleanup(shr_freep))) char *dump_name = NULL((void*)0);
794 __cleanup_free__attribute__((cleanup(shr_freep))) char *file_path = NULL((void*)0);
795
796 // [Data Area 4] Initialize
797 if ((data_area == 0) || (data_area == 4))
798 is_enabled_data_area4 = true1;
799
800 // [Data Area 4] 1. Check DA4S bit (Data Area 4 Support)
801 if (is_enabled_data_area4) {
802 get_da4s(ctrl, &da4s);
803 if (da4s != true1) {
804 /* Continue on if this fails, it's not a fatal condition */
805 printf("\nData Area 4 Support(DA4S) bit is 0.\n");
806 is_enabled_data_area4 = false0;
807 }
808 }
809
810 // [Data Area 4] 2. Check MCDAS bit and set MCDA only if creating host log
811 if (is_enabled_data_area4 && (lsp == 1)) {
812 err = get_mcdas(hdl, nsid, &mcdas);
813 if (err) {
814 /* Extract Data Area 4 regardless of MCDAS */
815 printf("\nGet Maximum Created Data Area Support(MCDAS) "
816 "bit is not supported. (0x%X)\n", err);
817 } else if (mcdas != true1) {
818 /* Extract Data Area 4 regardless of MCDAS */
819 printf("\nMaximum Created Data Area Support(MCDAS) bit is 0.\n");
820 } else {
821 // Set MCDA to 4 (Maximum Created Data Area)
822 lsp |= (4 << 1);
823 }
824 }
825
826 // [Data Area 4] 3. Set ETDAS bit (Extended Telemetry Data Area 4 Supported)
827 if (is_enabled_data_area4) {
828 err = libnvme_set_etdas(hdl, &host_behavior_changed);
829 if (err) {
830 /* Continue on if this fails, it's not a fatal condition */
831 printf("\nSet ETDAS bit is not supported. (0x%X)\n", err);
832 is_enabled_data_area4 = false0;
833 }
834 }
835
836 // [Data Area 4] Notify the user whether area 4 can be extracted
837 if ((data_area == 0) || (data_area == 4)) {
838 if (is_enabled_data_area4 != true1) {
839 printf("\nData Area 4 will not be extracted. "
840 "(It might be displayed as empty.)\n");
841 printf("To verify detailed information, please check "
842 "the above logs to confirm\n");
843 printf("that DA4S, MCDAS, and other settings are "
844 "configured correctly.\n\n");
845 } else
846 printf("\nSetup for Data Area 4 extraction is done.\n\n");
847 }
848
849 // Get Header
850 err = get_telemetry_header(hdl, nsid, tele_type, TELEMETRY_HEADER_SIZE512,
851 (void *)&log_header, lsp, rae);
852 if (err)
853 goto restore_etdas;
854
855 if (header_print)
856 print_telemetry_header(&log_header, tele_type);
857
858 if (asprintf(&dump_name, "Telemetry_%s_header_only", feature_name) < 0) {
859 err = SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
860 goto restore_etdas;
861 }
862 file_path = make_file_path(dump_name, file_name, sn);
863 if (!file_path) {
864 err = SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
865 goto restore_etdas;
866 }
867 err = open_write_close(file_path, (__u8 *)&log_header, TELEMETRY_HEADER_SIZE512);
868 if (err)
869 goto restore_etdas;
870
871 __s64 last_block[5] = {0,};
872 __u64 offset[5] = {0,};
873 __s64 size[5] = {TELEMETRY_HEADER_SIZE512, 0, 0, 0, 0};
874
875 last_block[1] = le16_to_cpu(log_header.data_area1_last_block);
876 last_block[2] = le16_to_cpu(log_header.data_area2_last_block);
877 last_block[3] = le16_to_cpu(log_header.data_area3_last_block);
878 last_block[4] = le32_to_cpu(log_header.data_area4_last_block);
879
880 for (int i = 1; i <= 4; i++) {
881 offset[i] = (last_block[i - 1] + 1) * TELEMETRY_HEADER_SIZE512;
882 size[i] = (last_block[i] - last_block[i - 1]) * TELEMETRY_BYTE_PER_BLOCK512;
883 }
884
885 int area_start, area_end;
886
887 if (data_area == 0) // extract all area if not designated
888 area_start = 1, area_end = 4;
889 else // extract designated area only
890 area_start = area_end = data_area;
891
892 for (int area = area_start; area <= area_end && err == 0; area++) {
893 printf("\n");
894 if (size[area] <= 0) {
895 printf("Telemetry %s Area %d is empty.\n", feature_name, area);
896 continue;
897 }
898
899 __cleanup_free__attribute__((cleanup(shr_freep))) char *area_name = NULL((void*)0);
900
901 if (asprintf(&area_name, "Telemetry_%s_Area_%d", feature_name, area) < 0) {
902 err = SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
903 break;
904 }
905 err = extract_telemetry_dump_data(area_name, file_name, sn, size[area],
906 transfer_size, hdl, nsid, tele_type, 0, offset[area], rae);
907 }
908
909restore_etdas:
910 // [Data Area 4] 4. Restore ETDAS bit (Extended Telemetry Data Area 4 Supported)
911 if (host_behavior_changed) {
912 host_behavior_changed = false0;
913
914 int restore_err = libnvme_clear_etdas(hdl, &host_behavior_changed);
915
916 if (restore_err) {
917 /* Continue on if this fails, it's not a fatal condition */
918 printf("\nRestore ETDAS bit is not supported. (0x%X)\n", restore_err);
919 }
920 }
921
922 return err;
923}
924
925static void print_telemetry_info(enum DUMP_TYPE tele_type, int data_area,
926 int lid, int transfer_size)
927{
928 printf("\n-------------------------------------------------------------\n");
929
930 if (tele_type == DUMP_TYPE_HOST_0)
931 printf("\nExtracting Telemetry Host 0 Dump ");
932 else if (tele_type == DUMP_TYPE_HOST_1)
933 printf("\nExtracting Telemetry Host 1 Dump ");
934 else
935 printf("\nExtracting Telemetry Controller Dump ");
936
937 if (data_area == 0)
938 printf("(Data Area 1 to 4)");
939 else
940 printf("(Data Area %d)", data_area);
941
942 printf("([Get Log] LID %Xh)", lid);
943 printf("(Xfer %dK)\n", transfer_size / 1024);
944}
945
946#define MERGE_MAX_AREA4 4
947#define MERGE_COPY_CHUNK(64 * 1024) (64 * 1024)
948
949static bool_Bool file_is_readable(const char *path)
950{
951 return !access(path, R_OK4);
952}
953
954/*
955 * Append the whole of path to the already open descriptor out.
956 * Return: 0 on success, -errno otherwise.
957 */
958static int append_file(int out, const char *path)
959{
960 char *buf;
961 int in;
962 int ret = 0;
963
964 in = shr_open_rawdata(path, O_RDONLY)open((path), (00));
965 if (in < 0)
966 return -errno(*__errno_location ());
967
968 buf = malloc(MERGE_COPY_CHUNK(64 * 1024));
969 if (!buf) {
970 close(in);
971 return -ENOMEM12;
972 }
973
974 for (;;) {
975 ssize_t n = read(in, buf, MERGE_COPY_CHUNK(64 * 1024));
976
977 if (n < 0) {
978 if (errno(*__errno_location ()) == EINTR4)
979 continue;
980 ret = -errno(*__errno_location ());
981 break;
982 }
983 if (n == 0)
984 break;
985
986 ret = shr_write_all(out, buf, (size_t)n);
987 if (ret)
988 break;
989 }
990
991 free(buf);
992 close(in);
993 return ret;
994}
995
996/*
997 * Concatenate the telemetry header and the data area dumps into a single
998 * file.
999 *
1000 * get_telemetry_dump() writes no file for an area the controller reports as
1001 * empty, so the merge list is built from the areas that are actually on
1002 * disk and the output is named after those alone. Doing this in process
1003 * rather than through "cat a b c > out" matters: the shell truncates -- and
1004 * so creates -- the output before cat runs, which left a 0-byte file when
1005 * no area had been retrieved, and otherwise a name promising areas the file
1006 * did not contain.
1007 */
1008static int merge_telemetry_log(const char *dump_save_dir, const char *sn,
1009 enum DUMP_TYPE tele_type, int total_area_num)
1010{
1011 char *area_file[MERGE_MAX_AREA4 + 1] = {0,};
1012 __cleanup_free__attribute__((cleanup(shr_freep))) char *output_file = NULL((void*)0);
1013 __cleanup_free__attribute__((cleanup(shr_freep))) char *area_list = NULL((void*)0);
1014 int found[MERGE_MAX_AREA4];
1015 int found_cnt = 0;
1016 const char *dump_type;
1017 int out;
1018 int err;
1019
1020 if (tele_type == DUMP_TYPE_HOST_0)
1021 dump_type = "Host(0)";
1022 else if (tele_type == DUMP_TYPE_HOST_1)
1023 dump_type = "Host(1)";
1024 else if (tele_type == DUMP_TYPE_CTLR)
1025 dump_type = "Controller";
1026 else
1027 return 0;
1028
1029 if (total_area_num < 1 || total_area_num > MERGE_MAX_AREA4)
1030 return SAMSUNG_GENERAL_INVALID_PARAMETER_ERROR-3;
1031
1032 err = SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
1033
1034 if (asprintf(&area_file[0], "%s%s_Telemetry_%s_header_only.bin",
1035 dump_save_dir, sn, dump_type) < 0) {
1036 area_file[0] = NULL((void*)0);
1037 goto free_names;
1038 }
1039
1040 for (int i = 1; i <= total_area_num; i++) {
1041 if (asprintf(&area_file[i], "%s%s_Telemetry_%s_Area_%d.bin",
1042 dump_save_dir, sn, dump_type, i) < 0) {
1043 area_file[i] = NULL((void*)0);
1044 goto free_names;
1045 }
1046
1047 if (file_is_readable(area_file[i]))
1048 found[found_cnt++] = i;
1049 }
1050
1051 /*
1052 * An area the controller reported as empty is never written, so
1053 * finding none means there is simply nothing to merge. A missing
1054 * header is a different matter and is left to fail below: it is
1055 * written before any area is, and a failure there aborts the dump
1056 * before the merge is ever reached.
1057 */
1058 if (!found_cnt) {
1059 printf("Nothing to merge: no data area was retrieved.\n");
1060 err = 0;
1061 goto free_names;
1062 }
1063
1064 /* Name the merged dump after the areas it really holds. */
1065 for (int i = 0; i < found_cnt; i++) {
1066 __cleanup_free__attribute__((cleanup(shr_freep))) char *prev = area_list;
1067
1068 if (asprintf(&area_list, "%s%s%d", prev ? prev : "",
1069 prev ? "+" : "", found[i]) < 0) {
1070 area_list = NULL((void*)0);
1071 goto free_names;
1072 }
1073 }
1074
1075 if (asprintf(&output_file, "%s%s_Telemetry_%s_Area_%s.bin",
1076 dump_save_dir, sn, dump_type, area_list) < 0) {
1077 output_file = NULL((void*)0);
1078 goto free_names;
1079 }
1080
1081 out = shr_open_rawdata(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0666)open((output_file), (01 | 0100 | 01000), 0666);
1082 if (out < 0) {
1083 err = SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
1084 goto free_names;
1085 }
1086
1087 err = 0;
1088 for (int i = 0; i <= found_cnt && !err; i++) {
1089 /* Index 0 is the header, which always leads the merge. */
1090 const char *src = i ? area_file[found[i - 1]] : area_file[0];
1091
1092 err = append_file(out, src);
1093 if (err)
1094 fprintf(stderrstderr, "%s: %s\n", src, strerror(-err));
1095 }
1096
1097 close(out);
1098
1099 if (err)
1100 err = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
1101 else
1102 printf("The log file was saved in \"%s\"\n", output_file);
1103
1104free_names:
1105 for (int i = 0; i <= MERGE_MAX_AREA4; i++)
1106 free(area_file[i]);
1107
1108 return err;
1109}
1110
1111/*
1112 * Retrieve one telemetry dump, reporting a failure as it happens.
1113 * Collection is best effort, so the status is returned for the sake of
1114 * try_get_telemetry_all(), which must not merge over a failed retrieval;
1115 * callers that only collect ignore it.
1116 */
1117static int try_get_telemetry(struct libnvme_transport_handle *hdl,
1118 struct nvme_id_ctrl *ctrl, char *dump_save_dir, char *sn,
1119 enum DUMP_TYPE tele_type, int data_area, bool_Bool header_print)
1120{
1121 int err = 0;
1122 int get_log_lid;
1123 int tele_xfer_size = UNIT_DATA_SIZE_127KB(127 * 1024);
1124
1125 if (tele_type == DUMP_TYPE_CTLR)
1126 get_log_lid = 0x08;
1127 else // Host-init
1128 get_log_lid = 0x07;
1129
1130 print_telemetry_info(tele_type, data_area, get_log_lid, tele_xfer_size);
1131
1132 err = get_telemetry_dump(hdl, ctrl, dump_save_dir, sn, tele_type,
1133 data_area, header_print, tele_xfer_size);
1134 if (err != 0)
1135 samsung_print_error(err);
1136
1137 return err;
1138}
1139
1140/*
1141 * Retrieve every data area of one telemetry type and merge the results.
1142 * A host dump is merged twice, into the 1+2 and the 1+2+3+4 view; the
1143 * controller dump only into the full one.
1144 */
1145static void try_get_telemetry_all(struct libnvme_transport_handle *hdl,
1146 struct nvme_id_ctrl *ctrl, char *dump_save_dir, char *sn,
1147 enum DUMP_TYPE tele_type)
1148{
1149 static const int host_merges[] = {2, 4};
1150 static const int ctlr_merges[] = {4};
1151 bool_Bool is_ctlr = tele_type == DUMP_TYPE_CTLR;
1152 const int *merges = is_ctlr ? ctlr_merges : host_merges;
1153 int nr_merges = is_ctlr ? 1 : 2;
1154
1155 /*
1156 * Nothing to merge if the retrieval failed, and merging anyway would
1157 * be worse than nothing: the area files of an earlier run may still
1158 * be on disk, and joining those would present stale data as a
1159 * complete dump.
1160 */
1161 if (try_get_telemetry(hdl, ctrl, dump_save_dir, sn, tele_type, 0, true1))
1162 return;
1163
1164 for (int i = 0; i < nr_merges; i++) {
1165 int err;
1166
1167 printf("\n-------------------------------------------------------------\n\n");
1168
1169 err = merge_telemetry_log(dump_save_dir, sn, tele_type, merges[i]);
1170 if (err != 0)
1171 samsung_print_error(err);
1172 }
1173}
1174
1175static int samsung_nvme_get_vendor_dump(struct libnvme_transport_handle *hdl,
1176 __u8 op_code, void *data, __u32 data_len, __u8 sub_op_code,
1177 __u8 op_type, __u32 offset)
1178{
1179 struct libnvme_passthru_cmd cmd = {
1180 .opcode = op_code,
1181 .addr = (__u64)(uintptr_t) data,
1182 .data_len = data_len,
1183 .cdw10 = data_len / 4,
1184 .cdw12 = (sub_op_code << 24) | op_type,
1185 .cdw14 = offset,
1186 .timeout_ms = 15 * 1000,
1187 };
1188
1189 return samsung_nvme_submit_admin_passthru(hdl, &cmd);
1190}
1191
1192static int get_vendor_dump_header(struct libnvme_transport_handle *hdl,
1193 __u8 op_code, __u8 op_type, __u8 *data, int *total_dump_size)
1194{
1195 int err = 0;
1196
1197 err = samsung_nvme_get_vendor_dump(hdl, op_code, data,
1198 UNIT_DATA_SIZE_8KB(8 * 1024), 8, op_type, 0);
1199 if (err != 0)
1200 return err;
1201
1202 *total_dump_size = (data[15] << 24) | (data[14] << 16)
1203 | (data[13] << 8) | data[12];
1204
1205 return err;
1206}
1207
1208static int get_vendor_dump_data(struct libnvme_transport_handle *hdl,
1209 __u8 op_code, __u8 op_type, __u8 *data, __u32 offset)
1210{
1211 return samsung_nvme_get_vendor_dump(hdl, op_code, data,
1212 UNIT_DATA_SIZE_32KB(32 * 1024), 8, op_type, offset);
1213}
1214
1215static int get_vendor_dump(struct libnvme_transport_handle *hdl,
1216 char *feature_name, char *file_name, char *sn,
1217 __u8 op_code, __u8 op_type)
1218{
1219 __cleanup_free__attribute__((cleanup(shr_freep))) char *file_path = NULL((void*)0);
1220 int err = 0;
1221 int output = 0;
1222 int offset = 0, total_dump_size = 0;
1223 struct timeval begin;
1224 __u8 *data = libnvme_alloc(UNIT_DATA_SIZE_32KB(32 * 1024));
1225
1226 if (!data)
1227 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
1228
1229 file_path = make_file_path(feature_name, file_name, sn);
1230 if (!file_path) {
1231 libnvme_free(data);
1232 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
1233 }
1234
1235 measure_time(&begin, true1);
1236
1237 err = get_vendor_dump_header(hdl, op_code, op_type, data, &total_dump_size);
1238 if (err != 0)
1239 goto end;
1240
1241 output = shr_open_rawdata(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666)open((file_path), (01 | 0100 | 01000), 0666);
1242 if (output < 0) {
1243 err = SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
1244 goto end;
1245 }
1246
1247 if (shr_write_all(output, data, UNIT_DATA_SIZE_8KB(8 * 1024))) {
1248 err = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
1249 goto close_output;
1250 }
1251
1252 for (offset = 1; offset <= total_dump_size; offset += 4) {
1253 memset(data, 0, UNIT_DATA_SIZE_32KB(32 * 1024));
1254 err = get_vendor_dump_data(hdl, op_code, op_type, data, offset);
1255 if (err < 0) // last piece returns 2 on some devices.
1256 goto close_output;
1257
1258 if (shr_write_all(output, data, UNIT_DATA_SIZE_32KB(32 * 1024))) {
1259 err = SAMSUNG_GENERAL_FILE_WRITE_ERROR-1;
1260 goto close_output;
1261 }
1262
1263 measure_loop_time(&begin, offset / 4, total_dump_size / 4);
1264 if (!g_hide_progress)
1265 shr_spinner(feature_name,
1266 offset / (float)total_dump_size, stdoutstdout);
1267 }
1268 /*
1269 * The completion line prints even under --hide-progress: it is what
1270 * marks the dump as complete in a captured terminal log.
1271 */
1272 shr_spinner(feature_name, 1.0, stdoutstdout);
1273 printf("\n");
1274 printf("The log file was saved in \"%s\"\n", file_path);
1275 measure_time(&begin, false0);
1276 err = 0;
1277
1278close_output:
1279 close(output);
1280
1281end:
1282 libnvme_free(data);
1283 return err;
1284}
1285
1286static int get_vendor_crash_dump_0xf7(struct libnvme_transport_handle *hdl,
1287 char *file_name, char *sn)
1288{
1289 return get_vendor_dump(hdl, "VendorCrashDump_0xF7", file_name, sn, 0xF7, 0);
1290}
1291
1292static int get_vendor_memory_dump_0xf7(struct libnvme_transport_handle *hdl,
1293 char *file_name, char *sn)
1294{
1295 return get_vendor_dump(hdl, "VendorMemoryDump_0xF7", file_name, sn, 0xF7, 1);
1296}
1297
1298static int get_vendor_debug_dump_0xf7(struct libnvme_transport_handle *hdl,
1299 char *file_name, char *sn)
1300{
1301 return get_vendor_dump(hdl, "VendorDebugDump_0xF7", file_name, sn, 0xF7, 2);
1302}
1303
1304static int get_vendor_crash_dump_0xf8(struct libnvme_transport_handle *hdl,
1305 char *file_name, char *sn)
1306{
1307 return get_vendor_dump(hdl, "VendorCrashDump_0xF8", file_name, sn, 0xF8, 0);
1308}
1309
1310static int get_vendor_memory_dump_0xf8(struct libnvme_transport_handle *hdl,
1311 char *file_name, char *sn)
1312{
1313 return get_vendor_dump(hdl, "VendorMemoryDump_0xF8", file_name, sn, 0xF8, 1);
1314}
1315
1316static int get_vendor_debug_dump_0xf8(struct libnvme_transport_handle *hdl,
1317 char *file_name, char *sn)
1318{
1319 return get_vendor_dump(hdl, "VendorDebugDump_0xF8", file_name, sn, 0xF8, 2);
1320}
1321
1322/*
1323 * Run one vendor dump. A status of 1 means the device does not implement
1324 * that particular dump, which is not a failure of the command.
1325 */
1326static void run_vendor_dump(int (*get_dump)(struct libnvme_transport_handle *,
1327 char *, char *),
1328 struct libnvme_transport_handle *hdl, const char *label,
1329 int op_code, char *dump_save_dir, char *sn)
1330{
1331 int err;
1332
1333 print_border(label, 'v', op_code, 0);
1334
1335 err = get_dump(hdl, dump_save_dir, sn);
1336 if (err == 1)
1337 fprintf(stderrstderr, "This device is not supported\n");
1338 else if (err != 0)
1339 samsung_print_error(err);
1340}
1341
1342/*
1343 * Translate one dump-type token (e.g. "host0") into a selection in @sel.
1344 * Called once per comma-separated token so -t accepts a list such as
1345 * "host0,ctlr,vendor". Returns 0 on success, or a SAMSUNG_GENERAL_* error
1346 * code if the token is not a recognized dump type.
1347 */
1348static int vs_internal_log_select_type(const char *tok, bool_Bool *sel)
1349{
1350 if (!strcmp(tok, "host0"))
1351 sel[DUMP_TYPE_HOST_0] = true1;
1352 else if (!strcmp(tok, "host1"))
1353 sel[DUMP_TYPE_HOST_1] = true1;
1354 else if (!strcmp(tok, "ctlr") || !strcmp(tok, "controller"))
1355 sel[DUMP_TYPE_CTLR] = true1;
1356 else if (!strcmp(tok, "vendor"))
1357 sel[DUMP_TYPE_VENDOR] = true1;
1358 else {
1359 printf("Invalid dump type: '%s'.\n", tok);
1360 return SAMSUNG_GENERAL_INVALID_PARAMETER_ERROR-3;
1361 }
1362
1363 return 0;
1364}
1365
1366/*
1367 * A dump type is extracted when either no -t was given (select_all, i.e.
1368 * extract everything) or it was explicitly named in sel[].
1369 */
1370#define WANT_DUMP(t) (select_all || sel[(t)])
1371
1372static int vs_internal_log(int argc, char **argv, struct command *acmd,
1373 struct plugin *plugin)
1374{
1375 const char *desc = "Retrieve and save internal firmware log.";
1376 const char *type = "Use this option if you want to extract a specific kind of dump.\n"
1377 "Comma-separate to extract several at once (no spaces).\n"
1378 "Example) -t host1 or -t host1,ctlr,vendor\n"
1379 "host0---Host_Initiated_Telemetry_Dump(LSP_0)\n"
1380 "host1---Host_Initiated_Telemetry_Dump(LSP_1)\n"
1381 "ctlr----Controller_Initiated_Telemetry_Dump\n"
1382 "vendor--Vendor_Dumps\n";
1383 const char *area = "Telemetry Data Area; 1 to 4. Default: 0(all).";
1384 const char *file = "Output file name with path;\n"
1385 "e.g. '-O ./path/name'\n'-O ./path1/path2/';\n"
1386 "If requested path does not exist, "
1387 "the directory will be newly created.";
1388 const char *compress = "Save dumps in a compressed file.";
1389 const char *hide_progress = "Hide the incremental extraction progress.\n"
1390 "The completion line of each dump is still printed.";
1391
1392 int err = 0;
1393 char sn[21] = {0,};
1394 struct nvme_id_ctrl ctrl = {0,};
1395 struct libnvme_passthru_cmd cmd;
1396 __cleanup_free__attribute__((cleanup(shr_freep))) char *dump_temp_dir = NULL((void*)0);
1397 __cleanup_free__attribute__((cleanup(shr_freep))) char *dump_save_dir = NULL((void*)0);
1398
1399 __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0);
1400 __cleanup_nvme_transport_handle__attribute__((cleanup(cleanup_nvme_transport_handle))) struct libnvme_transport_handle *hdl = NULL((void*)0);
1401
1402 bool_Bool sel[DUMP_TYPE_MAX] = { false0 };
1403 bool_Bool select_all = false0;
1404 int tele_data_area = 0;
1405
1406 struct config {
1407 char *type;
1408 int area;
1409 char *file;
1410 int compress;
1411 int hide_progress;
1412 };
1413
1414 struct config cfg = {
1415 .type = NULL((void*)0),
1416 .area = 0,
1417 .file = NULL((void*)0),
1418 .compress = 0,
1419 .hide_progress = 0,
1420 };
1421
1422 NVME_ARGS_OUTPUT_FORMATS(opts, NORMAL, "Output format: normal",nvme_args.supported_output_formats = NORMAL; struct argconfig_commandline_options
opts[] = { {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)
0), 0, "Options", 0, ((void*)0)}, {"dump-type", 't', "TYPE", CFG_STRING
, &cfg.type, 1, type, 0, }, {"telemetry-data-area", 'a', "NUM"
, CFG_INT, &cfg.area, 1, area, 0, }, {"output-file", 'O',
"FILE", CFG_STRING, &cfg.file, 1, file, 0, }, {"compress"
, 'z', ((void*)0), CFG_FLAG, &cfg.compress, 0, compress, 0
, }, {"hide-progress", 'H', ((void*)0), CFG_FLAG, &cfg.hide_progress
, 0, hide_progress, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR
, ((void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose"
, 'v', "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity"
, 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet
, 0, "suppress informational output", 0, }, {"output-format",
'o', "FMT", CFG_STRING, &nvme_args.output_format, 1, "Output format: normal"
, 0, }, {"timeout", 0, "NUM", CFG_POSITIVE, &nvme_args.timeout
, 1, "timeout value, in milliseconds", 0, }, {"dry-run", 0, (
(void*)0), CFG_FLAG, &nvme_args.dry_run, 0, "show command instead of executing"
, 0, }, {"no-retries", 0, ((void*)0), CFG_FLAG, &nvme_args
.no_retries, 0, "disable retry logic on errors", 0, }, {"no-ioctl-probing"
, 0, ((void*)0), CFG_FLAG, &nvme_args.no_ioctl_probing, 0
, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version"
, 0, "NUM", CFG_POSITIVE, &nvme_args.output_format_ver, 1
, "output format version: 1|2", 0, }, {"human-readable", 'H',
((void*)0), CFG_FLAG, &nvme_args.verbose, 0, ((void*)0),
0, ((void*)0), 1}, {"set-options", 0, "KEY=VALUE", CFG_STRING
, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);"
, 0, }, { ((void*)0) } }
1423 OPT_STRING("dump-type", 't', "TYPE", &cfg.type, type),nvme_args.supported_output_formats = NORMAL; struct argconfig_commandline_options
opts[] = { {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)
0), 0, "Options", 0, ((void*)0)}, {"dump-type", 't', "TYPE", CFG_STRING
, &cfg.type, 1, type, 0, }, {"telemetry-data-area", 'a', "NUM"
, CFG_INT, &cfg.area, 1, area, 0, }, {"output-file", 'O',
"FILE", CFG_STRING, &cfg.file, 1, file, 0, }, {"compress"
, 'z', ((void*)0), CFG_FLAG, &cfg.compress, 0, compress, 0
, }, {"hide-progress", 'H', ((void*)0), CFG_FLAG, &cfg.hide_progress
, 0, hide_progress, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR
, ((void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose"
, 'v', "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity"
, 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet
, 0, "suppress informational output", 0, }, {"output-format",
'o', "FMT", CFG_STRING, &nvme_args.output_format, 1, "Output format: normal"
, 0, }, {"timeout", 0, "NUM", CFG_POSITIVE, &nvme_args.timeout
, 1, "timeout value, in milliseconds", 0, }, {"dry-run", 0, (
(void*)0), CFG_FLAG, &nvme_args.dry_run, 0, "show command instead of executing"
, 0, }, {"no-retries", 0, ((void*)0), CFG_FLAG, &nvme_args
.no_retries, 0, "disable retry logic on errors", 0, }, {"no-ioctl-probing"
, 0, ((void*)0), CFG_FLAG, &nvme_args.no_ioctl_probing, 0
, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version"
, 0, "NUM", CFG_POSITIVE, &nvme_args.output_format_ver, 1
, "output format version: 1|2", 0, }, {"human-readable", 'H',
((void*)0), CFG_FLAG, &nvme_args.verbose, 0, ((void*)0),
0, ((void*)0), 1}, {"set-options", 0, "KEY=VALUE", CFG_STRING
, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);"
, 0, }, { ((void*)0) } }
1424 OPT_INT("telemetry-data-area", 'a', &cfg.area, area),nvme_args.supported_output_formats = NORMAL; struct argconfig_commandline_options
opts[] = { {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)
0), 0, "Options", 0, ((void*)0)}, {"dump-type", 't', "TYPE", CFG_STRING
, &cfg.type, 1, type, 0, }, {"telemetry-data-area", 'a', "NUM"
, CFG_INT, &cfg.area, 1, area, 0, }, {"output-file", 'O',
"FILE", CFG_STRING, &cfg.file, 1, file, 0, }, {"compress"
, 'z', ((void*)0), CFG_FLAG, &cfg.compress, 0, compress, 0
, }, {"hide-progress", 'H', ((void*)0), CFG_FLAG, &cfg.hide_progress
, 0, hide_progress, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR
, ((void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose"
, 'v', "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity"
, 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet
, 0, "suppress informational output", 0, }, {"output-format",
'o', "FMT", CFG_STRING, &nvme_args.output_format, 1, "Output format: normal"
, 0, }, {"timeout", 0, "NUM", CFG_POSITIVE, &nvme_args.timeout
, 1, "timeout value, in milliseconds", 0, }, {"dry-run", 0, (
(void*)0), CFG_FLAG, &nvme_args.dry_run, 0, "show command instead of executing"
, 0, }, {"no-retries", 0, ((void*)0), CFG_FLAG, &nvme_args
.no_retries, 0, "disable retry logic on errors", 0, }, {"no-ioctl-probing"
, 0, ((void*)0), CFG_FLAG, &nvme_args.no_ioctl_probing, 0
, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version"
, 0, "NUM", CFG_POSITIVE, &nvme_args.output_format_ver, 1
, "output format version: 1|2", 0, }, {"human-readable", 'H',
((void*)0), CFG_FLAG, &nvme_args.verbose, 0, ((void*)0),
0, ((void*)0), 1}, {"set-options", 0, "KEY=VALUE", CFG_STRING
, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);"
, 0, }, { ((void*)0) } }
1425 OPT_FILE("output-file", 'O', &cfg.file, file),nvme_args.supported_output_formats = NORMAL; struct argconfig_commandline_options
opts[] = { {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)
0), 0, "Options", 0, ((void*)0)}, {"dump-type", 't', "TYPE", CFG_STRING
, &cfg.type, 1, type, 0, }, {"telemetry-data-area", 'a', "NUM"
, CFG_INT, &cfg.area, 1, area, 0, }, {"output-file", 'O',
"FILE", CFG_STRING, &cfg.file, 1, file, 0, }, {"compress"
, 'z', ((void*)0), CFG_FLAG, &cfg.compress, 0, compress, 0
, }, {"hide-progress", 'H', ((void*)0), CFG_FLAG, &cfg.hide_progress
, 0, hide_progress, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR
, ((void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose"
, 'v', "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity"
, 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet
, 0, "suppress informational output", 0, }, {"output-format",
'o', "FMT", CFG_STRING, &nvme_args.output_format, 1, "Output format: normal"
, 0, }, {"timeout", 0, "NUM", CFG_POSITIVE, &nvme_args.timeout
, 1, "timeout value, in milliseconds", 0, }, {"dry-run", 0, (
(void*)0), CFG_FLAG, &nvme_args.dry_run, 0, "show command instead of executing"
, 0, }, {"no-retries", 0, ((void*)0), CFG_FLAG, &nvme_args
.no_retries, 0, "disable retry logic on errors", 0, }, {"no-ioctl-probing"
, 0, ((void*)0), CFG_FLAG, &nvme_args.no_ioctl_probing, 0
, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version"
, 0, "NUM", CFG_POSITIVE, &nvme_args.output_format_ver, 1
, "output format version: 1|2", 0, }, {"human-readable", 'H',
((void*)0), CFG_FLAG, &nvme_args.verbose, 0, ((void*)0),
0, ((void*)0), 1}, {"set-options", 0, "KEY=VALUE", CFG_STRING
, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);"
, 0, }, { ((void*)0) } }
1426 OPT_FLAG("compress", 'z', &cfg.compress, compress),nvme_args.supported_output_formats = NORMAL; struct argconfig_commandline_options
opts[] = { {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)
0), 0, "Options", 0, ((void*)0)}, {"dump-type", 't', "TYPE", CFG_STRING
, &cfg.type, 1, type, 0, }, {"telemetry-data-area", 'a', "NUM"
, CFG_INT, &cfg.area, 1, area, 0, }, {"output-file", 'O',
"FILE", CFG_STRING, &cfg.file, 1, file, 0, }, {"compress"
, 'z', ((void*)0), CFG_FLAG, &cfg.compress, 0, compress, 0
, }, {"hide-progress", 'H', ((void*)0), CFG_FLAG, &cfg.hide_progress
, 0, hide_progress, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR
, ((void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose"
, 'v', "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity"
, 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet
, 0, "suppress informational output", 0, }, {"output-format",
'o', "FMT", CFG_STRING, &nvme_args.output_format, 1, "Output format: normal"
, 0, }, {"timeout", 0, "NUM", CFG_POSITIVE, &nvme_args.timeout
, 1, "timeout value, in milliseconds", 0, }, {"dry-run", 0, (
(void*)0), CFG_FLAG, &nvme_args.dry_run, 0, "show command instead of executing"
, 0, }, {"no-retries", 0, ((void*)0), CFG_FLAG, &nvme_args
.no_retries, 0, "disable retry logic on errors", 0, }, {"no-ioctl-probing"
, 0, ((void*)0), CFG_FLAG, &nvme_args.no_ioctl_probing, 0
, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version"
, 0, "NUM", CFG_POSITIVE, &nvme_args.output_format_ver, 1
, "output format version: 1|2", 0, }, {"human-readable", 'H',
((void*)0), CFG_FLAG, &nvme_args.verbose, 0, ((void*)0),
0, ((void*)0), 1}, {"set-options", 0, "KEY=VALUE", CFG_STRING
, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);"
, 0, }, { ((void*)0) } }
1427 OPT_FLAG("hide-progress", 'H', &cfg.hide_progress, hide_progress))nvme_args.supported_output_formats = NORMAL; struct argconfig_commandline_options
opts[] = { {"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)
0), 0, "Options", 0, ((void*)0)}, {"dump-type", 't', "TYPE", CFG_STRING
, &cfg.type, 1, type, 0, }, {"telemetry-data-area", 'a', "NUM"
, CFG_INT, &cfg.area, 1, area, 0, }, {"output-file", 'O',
"FILE", CFG_STRING, &cfg.file, 1, file, 0, }, {"compress"
, 'z', ((void*)0), CFG_FLAG, &cfg.compress, 0, compress, 0
, }, {"hide-progress", 'H', ((void*)0), CFG_FLAG, &cfg.hide_progress
, 0, hide_progress, 0, }, {"", 0, ((void*)0), CFG_GROUP_SEPARATOR
, ((void*)0), 0, "Global options", 0, ((void*)0)}, {"verbose"
, 'v', "NUM", CFG_INCREMENT, &nvme_args.verbose, 0, "Increase output verbosity"
, 0, }, {"quiet", 0, ((void*)0), CFG_FLAG, &nvme_args.quiet
, 0, "suppress informational output", 0, }, {"output-format",
'o', "FMT", CFG_STRING, &nvme_args.output_format, 1, "Output format: normal"
, 0, }, {"timeout", 0, "NUM", CFG_POSITIVE, &nvme_args.timeout
, 1, "timeout value, in milliseconds", 0, }, {"dry-run", 0, (
(void*)0), CFG_FLAG, &nvme_args.dry_run, 0, "show command instead of executing"
, 0, }, {"no-retries", 0, ((void*)0), CFG_FLAG, &nvme_args
.no_retries, 0, "disable retry logic on errors", 0, }, {"no-ioctl-probing"
, 0, ((void*)0), CFG_FLAG, &nvme_args.no_ioctl_probing, 0
, "disable 64-bit IOCTL support probing", 0, }, {"output-format-version"
, 0, "NUM", CFG_POSITIVE, &nvme_args.output_format_ver, 1
, "output format version: 1|2", 0, }, {"human-readable", 'H',
((void*)0), CFG_FLAG, &nvme_args.verbose, 0, ((void*)0),
0, ((void*)0), 1}, {"set-options", 0, "KEY=VALUE", CFG_STRING
, &nvme_args.set_options, 1, "set a libnvme library option (key=value[,key=value,...]);"
, 0, }, { ((void*)0) } }
;
1428
1429 samsung_initialize();
1430
1431 err = parse_and_open(&ctx, &hdl, argc, argv, desc, opts);
1432 if (err)
1433 return err;
1434
1435 nvme_init_identify_ctrl(&cmd, &ctrl);
1436 err = libnvme_exec_admin_passthru(hdl, &cmd);
1437 if (err) {
1438 samsung_print_error(err);
1439 return err;
1440 }
1441
1442 if (le16_to_cpu(ctrl.vid) != 0x144d) {
1443 samsung_print_error(SAMSUNG_GENERAL_INVALID_VID_ERROR-4);
1444 fprintf(stderrstderr, "Current Vendor ID: 0x%X\n", le16_to_cpu(ctrl.vid));
1445 return SAMSUNG_GENERAL_INVALID_VID_ERROR-4;
1446 }
1447
1448 get_serial_number(&ctrl, sn);
1449
1450 /*
1451 * -a applies to the telemetry dumps whether or not -t was given, so
1452 * validate it before the dump type selection.
1453 */
1454 tele_data_area = cfg.area;
1455 if (tele_data_area < 0 || tele_data_area > 4) {
1456 fprintf(stderrstderr, "\nUnsupported data area entered. "
1457 "Valid range: 0 to 4.\n");
1458 samsung_print_error(SAMSUNG_GENERAL_INVALID_PARAMETER_ERROR-3);
1459 return SAMSUNG_GENERAL_INVALID_PARAMETER_ERROR-3;
1460 }
1461
1462 if (!cfg.type)
1463 select_all = true1;
1464 else {
1465 __cleanup_free__attribute__((cleanup(shr_freep))) char *type_buf = NULL((void*)0);
1466 char *tok, *saveptr = NULL((void*)0);
1467
1468 /*
1469 * -t accepts a comma-separated list, e.g. "host0,ctlr,vendor".
1470 * strtok_r() mutates its input, so parse a copy of cfg.type
1471 * rather than the option string itself.
1472 */
1473 type_buf = strdup(cfg.type);
1474 if (!type_buf)
1475 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
1476
1477 for (tok = strtok_r(type_buf, ",", &saveptr); tok != NULL((void*)0);
1478 tok = strtok_r(NULL((void*)0), ",", &saveptr)) {
1479 err = vs_internal_log_select_type(tok, sel);
1480 if (err != 0) {
1481 samsung_print_error(err);
1482 return err;
1483 }
1484 }
1485 }
1486
1487 /*
1488 * -O is a file name prefix: make_file_path() concatenates it onto the
1489 * generated name without inserting a separator, so "./dumps/" yields
1490 * "./dumps/<sn>_..." and "./dumps/run1" yields "./dumps/run1<sn>_...".
1491 * Either way the directories end at the last '/', which is what
1492 * shr_mkdir_from_fname() creates. A bare "run1" has no directory
1493 * part and reduces to creating ".", a harmless no-op.
1494 */
1495 if (cfg.file) {
1496 err = shr_mkdir_from_fname(cfg.file, 0777);
1497 if (err < 0) {
1498 fprintf(stderrstderr, "mkdir for %s: %s\n", cfg.file,
1499 strerror(-err));
1500 return SAMSUNG_GENERAL_FILE_OPEN_ERROR-2;
1501 }
1502 }
1503
1504 if (cfg.compress) {
1505 err = insert_dir(cfg.file, "temp_samsung_dumps",
1506 &dump_temp_dir, &dump_save_dir);
1507 if (err != 0) {
1508 samsung_print_error(err);
1509 return err;
1510 }
1511 } else {
1512 dump_save_dir = strdup(cfg.file ? cfg.file : "./");
1513 if (!dump_save_dir)
1514 return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5;
1515 }
1516
1517 g_hide_progress = cfg.hide_progress;
1518
1519 /*
1520 * Collection is best effort: every dump is attempted and each failure
1521 * is reported where it happens, so one unsupported or failing dump
1522 * does not cost the others.
1523 */
1524
1525 /*
1526 * Host(0) is intentionally excluded from "all" dumps (only run when
1527 * explicitly selected), so it is gated on sel[] directly rather than
1528 * WANT_DUMP()/select_all.
1529 */
1530 if (sel[DUMP_TYPE_HOST_0]) {
1531 if (tele_data_area == 0)
1532 try_get_telemetry_all(hdl, &ctrl, dump_save_dir, sn,
1533 DUMP_TYPE_HOST_0);
1534 else
1535 try_get_telemetry(hdl, &ctrl, dump_save_dir, sn,
1536 DUMP_TYPE_HOST_0, tele_data_area, true1);
1537 }
1538
1539 if (WANT_DUMP(DUMP_TYPE_HOST_1)) {
1540 if (tele_data_area == 0)
1541 try_get_telemetry_all(hdl, &ctrl, dump_save_dir, sn,
1542 DUMP_TYPE_HOST_1);
1543 else
1544 try_get_telemetry(hdl, &ctrl, dump_save_dir, sn,
1545 DUMP_TYPE_HOST_1, tele_data_area, true1);
1546 }
1547
1548 if (WANT_DUMP(DUMP_TYPE_CTLR)) {
1549 if (tele_data_area == 0)
1550 try_get_telemetry_all(hdl, &ctrl, dump_save_dir, sn,
1551 DUMP_TYPE_CTLR);
1552 else
1553 try_get_telemetry(hdl, &ctrl, dump_save_dir, sn,
1554 DUMP_TYPE_CTLR, tele_data_area, true1);
1555 }
1556
1557 if (WANT_DUMP(DUMP_TYPE_VENDOR)) {
1558 run_vendor_dump(get_vendor_crash_dump_0xf7, hdl,
1559 "Vendor Crash Dump", 0xF7, dump_save_dir, sn);
1560 run_vendor_dump(get_vendor_crash_dump_0xf8, hdl,
1561 "Vendor Crash Dump", 0xF8, dump_save_dir, sn);
1562 run_vendor_dump(get_vendor_memory_dump_0xf7, hdl,
1563 "Vendor Memory Dump", 0xF7, dump_save_dir, sn);
1564 run_vendor_dump(get_vendor_memory_dump_0xf8, hdl,
1565 "Vendor Memory Dump", 0xF8, dump_save_dir, sn);
1566 run_vendor_dump(get_vendor_debug_dump_0xf7, hdl,
1567 "Vendor Debug Dump", 0xF7, dump_save_dir, sn);
1568 run_vendor_dump(get_vendor_debug_dump_0xf8, hdl,
1569 "Vendor Debug Dump", 0xF8, dump_save_dir, sn);
1570 }
1571
1572 printf("\n-------------------------------------------------------------\n");
1573
1574 /*
1575 * Archiving is the one step whose failure is reported through the
1576 * exit status: without it the user does not have the file they asked
1577 * -z for, while the dumps themselves are already on disk.
1578 */
1579 err = 0;
1580 if (cfg.compress) {
1581 err = compress_dump_files(dump_temp_dir, cfg.file, sn);
1582 if (err != 0)
1583 samsung_print_error(err);
1584 }
1585
1586 printf("vs-internal-log done.\n");
1587
1588 return err;
1589}
1590
1591#undef WANT_DUMP
1592
1593static struct command vs_internal_log_cmd = {
1594 .name = "vs-internal-log",
1595 .help = "Retrieve and save internal firmware log",
1596 .fn = vs_internal_log,
1597};
1598
1599static struct command *commands[] = {
1600 &vs_internal_log_cmd,
1601 NULL((void*)0),
1602};
1603
1604static struct plugin plugin = {
1605 .name = "samsung",
1606 .desc = "Samsung vendor specific extensions",
1607 .version = SAMSUNG_PLUGIN_VERSION"3.0.15",
1608};
1609
1610static void __shr_constructor__attribute__((constructor)) register_plugin(void)
1611{
1612 plugin_add_group(&plugin, NULL((void*)0), commands);
1613 register_extension(&plugin);
1614}