Bug Summary

File:.build-ci/../plugins/samsung/samsung-nvme.c
Warning:line 1451, column 2
Potential leak of memory pointed to by 'dump_save_dir'

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