| File: | .build-ci/../plugins/samsung/samsung-nvme.c |
| Warning: | line 1477, column 12 Potential leak of memory pointed to by 'type_buf' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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 | ||||
| 36 | static bool_Bool g_hide_progress; | |||
| 37 | ||||
| 38 | static 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 | ||||
| 64 | static 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 | ||||
| 78 | static 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 | ||||
| 85 | static 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 | ||||
| 91 | static 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 | */ | |||
| 129 | static 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 | */ | |||
| 141 | static 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 | ||||
| 153 | static 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 | ||||
| 179 | static 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 | */ | |||
| 259 | static 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 | */ | |||
| 315 | static 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. */ | |||
| 350 | struct dump_names { | |||
| 351 | char **name; | |||
| 352 | size_t count; | |||
| 353 | }; | |||
| 354 | ||||
| 355 | static 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 | */ | |||
| 374 | static 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 | ||||
| 426 | static 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 | ||||
| 486 | static 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 | */ | |||
| 504 | enum 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 | ||||
| 519 | struct 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 | ||||
| 528 | struct 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 | ||||
| 547 | static 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 | |||
| 601 | static 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 | |||
| 609 | static 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 | ||||
| 627 | static 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 | */ | |||
| 639 | static 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)); | |||
| 658 | if (!data) | |||
| 659 | return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5; | |||
| 660 | ||||
| 661 | if (asprintf(&feature_name_header, "%s_header", feature_name) < 0) | |||
| 662 | return SAMSUNG_GENERAL_MEM_ALLOC_ERROR-5; | |||
| 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 | ||||
| 734 | close_output: | |||
| 735 | if (output >= 0) | |||
| 736 | close(output); | |||
| 737 | ||||
| 738 | close_output_header: | |||
| 739 | if (output_header >= 0) | |||
| 740 | close(output_header); | |||
| 741 | ||||
| 742 | end: | |||
| 743 | return err; | |||
| 744 | } | |||
| 745 | ||||
| 746 | static 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 | ||||
| 764 | static 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 | ||||
| 909 | restore_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 | ||||
| 925 | static 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 | ||||
| 949 | static 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 | */ | |||
| 958 | static 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 | */ | |||
| 1008 | static 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 | ||||
| 1104 | free_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 | */ | |||
| 1117 | static 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 | */ | |||
| 1145 | static 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 | ||||
| 1175 | static 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 | ||||
| 1192 | static 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 | ||||
| 1208 | static 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 | ||||
| 1215 | static 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 | ||||
| 1278 | close_output: | |||
| 1279 | close(output); | |||
| 1280 | ||||
| 1281 | end: | |||
| 1282 | libnvme_free(data); | |||
| 1283 | return err; | |||
| 1284 | } | |||
| 1285 | ||||
| 1286 | static 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 | ||||
| 1292 | static 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 | ||||
| 1298 | static 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 | ||||
| 1304 | static 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 | ||||
| 1310 | static 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 | ||||
| 1316 | static 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 | */ | |||
| 1326 | static 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 | */ | |||
| 1348 | static 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 | ||||
| 1372 | static 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 | ||||
| 1593 | static 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 | ||||
| 1599 | static struct command *commands[] = { | |||
| 1600 | &vs_internal_log_cmd, | |||
| 1601 | NULL((void*)0), | |||
| 1602 | }; | |||
| 1603 | ||||
| 1604 | static struct plugin plugin = { | |||
| 1605 | .name = "samsung", | |||
| 1606 | .desc = "Samsung vendor specific extensions", | |||
| 1607 | .version = SAMSUNG_PLUGIN_VERSION"3.0.15", | |||
| 1608 | }; | |||
| 1609 | ||||
| 1610 | static void __shr_constructor__attribute__((constructor)) register_plugin(void) | |||
| 1611 | { | |||
| 1612 | plugin_add_group(&plugin, NULL((void*)0), commands); | |||
| 1613 | register_extension(&plugin); | |||
| 1614 | } |