Files
s390-tools/zfcpdump/zfcpdump_part.c
Philipp Rudo f02de298c1 zfcpdump: Remove module load
The zfcpdump kernel is built without module support. Furthermore the initrd
doesn't contain modprobe. So trying to load modules using modprobe in
zfcpdump userspace doesn't really make sense.

Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
Reviewed-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-09-04 14:09:24 +02:00

457 lines
11 KiB
C

/*
* zfcpdump - Write /proc/vmcore to SCSI partition
*
* This tool should be used in an intitramfs together with a kernel with
* enabled CONFIG_ZFCPDUMP kernel build option. The tool is able to write
* standalone system dumps on SCSI disks.
*
* See Documentation/s390/zfcpdump.txt for more information!
*
* Copyright IBM Corp. 2003, 2017
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <asm/types.h>
#include <ctype.h>
#include <dirent.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/hdreg.h>
#include <linux/reboot.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "lib/util_base.h"
#include "lib/zt_common.h"
#include "zfcpdump.h"
#define COPY_BUF_SIZE 0x10000UL
#define COPY_TABLE_ENTRY_COUNT 4
/*
* Copy table entry
*/
struct copy_table_entry {
unsigned long size;
unsigned long off;
};
/*
* Single volume SCSI dump superblock
*/
struct scsi_dump_sb {
uint64_t magic;
uint64_t version;
uint64_t part_start;
uint64_t part_size;
uint64_t dump_off;
uint64_t dump_size;
uint64_t csum_off;
uint64_t csum_size;
uint64_t csum;
} __attribute((packed));
/*
* Layout of SCSI disk block pointer
*/
struct linear_blockptr {
uint64_t blockno;
uint16_t size;
uint16_t blockct;
uint8_t reserved[4];
} __attribute((packed));
/* From boot.h in zipl */
struct boot_info {
char magic[4];
uint8_t version;
uint8_t bp_type;
uint8_t dev_type;
uint8_t flags;
uint64_t sb_off;
} __attribute__ ((packed));
/* From install.c in zipl */
struct scsi_mbr {
uint8_t magic[4];
uint32_t version_id;
uint8_t reserved[8];
struct linear_blockptr lin;
uint8_t reserverd[0x50];
struct boot_info boot_info;
} __attribute__ ((packed));
/* From boot.h in zipl */
#define BOOT_INFO_VERSION 1
#define BOOT_INFO_MAGIC "zIPL"
#define BOOT_INFO_DEV_TYPE_SCSI 0x02
#define BOOT_INFO_BP_TYPE_DUMP 0x01
/*
* Globals
*/
static struct scsi_dump_sb dump_sb;
static struct scsi_mbr mbr;
/*
* Read file at given offset
*/
static int pread_file(const char *path, char *buf, int size, uint64_t off)
{
int fd;
PRINT_TRACE("Read: %s:\n", path);
fd = open(path, O_RDONLY);
if (fd == -1) {
PRINT_PERR("open %s failed\n", path);
return -1;
}
if (lseek(fd, off, SEEK_SET) < 0) {
PRINT_PERR("seek %s offset %llu failed\n", path,
(unsigned long long) off);
return -1;
}
if (read(fd, buf, size) < 0) {
PRINT_PERR("read %s failed\n", path);
close(fd);
return -1;
}
close(fd);
return 0;
}
/*
* Create checksum for buffer
*/
static inline uint32_t csum_partial(const void *buf, int len, uint32_t sum)
{
register unsigned long reg2 asm("2") = (unsigned long) buf;
register unsigned long reg3 asm("3") = (unsigned long) len;
asm volatile(
"0: cksm %0,%1\n" /* do checksum on longs */
" jo 0b\n"
: "+d" (sum), "+d" (reg2), "+d" (reg3) : : "cc",
"memory");
return sum;
}
/*
* Create checksum on SCSI device
*/
static int csum_get(uint64_t off, uint64_t len, uint64_t *result)
{
char buf[len];
if (pread_file(DEV_SCSI, (char *)&buf, sizeof(buf), off) < 0) {
PRINT_ERR("Error reading checksum from disk\n");
return -1;
}
*result = (uint64_t)csum_partial(&buf, len, 0x12345678);
PRINT_TRACE("Got crc %llx\n", (unsigned long long) *result);
return 0;
}
/*
* Update superblock checksum on SCSI disk
*/
static int csum_update(int fd)
{
/* Write crc into zfcpdump header */
if (csum_get(dump_sb.part_start + dump_sb.csum_off,
dump_sb.csum_size, &dump_sb.csum)) {
PRINT_ERR("Get check sum failed\n");
return -1;
}
if (lseek(fd, mbr.boot_info.sb_off, SEEK_SET) < 0) {
PRINT_PERR("Seek failed\n");
return -1;
}
if (write(fd, &dump_sb, sizeof(dump_sb)) < 0) {
PRINT_PERR("Write failed\n");
return -1;
}
return 0;
}
/*
* Initialize copy table to copy HSA first
*/
static int copy_table_init(int fd, struct copy_table_entry *table)
{
Elf64_Ehdr ehdr;
Elf64_Phdr phdr;
int i;
g.vmcore_size = lseek(fd, (off_t) 0, SEEK_END);
lseek(fd, 0L, SEEK_SET);
if (g.vmcore_size < sizeof(ehdr))
return -1;
if (read(fd, &ehdr, sizeof(ehdr)) < 0)
return -1;
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0)
return -1;
if (ehdr.e_type != ET_CORE)
return -1;
if (ehdr.e_machine != EM_S390 || ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
PRINT_ERR("Only 64 bit core dump files are supported\n");
return -1;
}
/* Find first memory chunk to determine HSA size */
for (i = 0; i < ehdr.e_phnum; i++) {
if (read(fd, &phdr, sizeof(phdr)) < 0)
return -1;
if (phdr.p_type != PT_LOAD)
continue;
if (phdr.p_vaddr != 0)
continue;
/* 1st entry is the HSA (vaddr = 0) */
table[0].off = phdr.p_offset;
table[0].size = get_hsa_size();
/* 2nd entry defines area from 2nd page to HSA start */
table[1].off = PAGE_SIZE;
table[1].size = table[0].off - PAGE_SIZE;
/*
* 3rd entry defines area from HSA end to end of file.
*/
table[2].off = table[0].off + table[0].size;
table[2].size = g.vmcore_size - table[2].off;
/*
* We write the last page at the end of the dump processing.
* This ensures that the dump stays invalid until all data
* is written. We can use one page because it is ensured that
* the HSA starts page aligned.
*/
table[3].off = 0;
table[3].size = PAGE_SIZE;
return 0;
}
PRINT_ERR("Could not find HSA ELF load section\n");
return -1;
}
/*
* Copy one copy table entry form /proc/vmcore to dump partition
*/
static int copy_table_entry_write(int fdin, int fdout,
struct copy_table_entry *entry,
unsigned long offset)
{
unsigned long buf_size, bytes_left, off;
void *map;
if (entry->size == 0)
return 0;
off = entry->off;
bytes_left = entry->size;
if (lseek(fdout, entry->off + offset, SEEK_SET) < 0)
return -1;
while (bytes_left > 0) {
buf_size = MIN(COPY_BUF_SIZE, bytes_left);
map = mmap(0, buf_size, PROT_READ, MAP_SHARED, fdin, off);
if (map == (void *)-1) {
PRINT_PERR("Mapping failed\n");
return -1;
}
if (write(fdout, map, buf_size) < 0) {
PRINT_PERR("Write to partition failed\n");
return -1;
}
munmap(map, buf_size);
bytes_left -= buf_size;
off += buf_size;
show_progress(buf_size);
}
return 0;
}
/*
* Copy dump using mmap (copy HSA first)
*/
static int copy_dump(const char *in, const char *out, unsigned long offset)
{
struct copy_table_entry table[COPY_TABLE_ENTRY_COUNT];
char busy_str[] = "zfcpdump busy";
int fdout, fdin, i, rc = -1;
fdin = open(in, O_RDONLY);
if (fdin < 0) {
PRINT_ERR("Open %s failed\n", in);
return -1;
}
g.vmcore_size = lseek(fdin, (off_t) 0, SEEK_END);
lseek(fdin, 0L, SEEK_SET);
if (g.vmcore_size > dump_sb.dump_size) {
PRINT_ERR("Disk too small: dump=%lldMB (diskspace=%lldMB)\n",
TO_MIB(g.vmcore_size), TO_MIB(dump_sb.dump_size));
goto out_close_fdin;
}
fdout = open(out, O_WRONLY);
if (fdout < 0) {
PRINT_ERR("Open %s failed\n", out);
goto out_close_fdin;
}
/* Overwrite old header */
if (lseek(fdout, offset, SEEK_SET) < 0)
goto out_close_fdin;
if (write(fdout, busy_str, sizeof(busy_str)) == -1)
goto out_close_fdin;
if (csum_update(fdout))
goto out_close_fdin;
if (copy_table_init(fdin, table))
goto out_close_fdin;
show_progress(0);
for (i = 0; i < COPY_TABLE_ENTRY_COUNT; i++) {
if (copy_table_entry_write(fdin, fdout, &table[i], offset))
goto out_close_fdout;
if (i == 0) /* 0 is the HSA */
release_hsa();
}
rc = 0;
out_close_fdout:
if (csum_update(fdout))
rc = -1;
fsync(fdout);
close(fdout);
out_close_fdin:
close(fdin);
return rc;
}
/*
* Finds the matching partition to a given start and end. If a matching
* partition is found, the partition number is returned.
*/
int find_part_num(uint64_t start, uint64_t size)
{
struct hd_geometry geo;
uint32_t block_size;
uint64_t part_size;
char path[11];
int fd, i;
PRINT_TRACE("Partiton to dump start: 0x%llx end: 0x%llx\n",
(unsigned long long) start, (unsigned long long) size);
for (i = 1; i < 16; i++) {
snprintf(path, sizeof(path), DEV_SCSI "%d", i);
fd = open(path, O_RDONLY);
if (fd == -1)
continue;
if (ioctl(fd, HDIO_GETGEO, &geo) != 0) {
PRINT_PERR("Could not retrieve partition"
" geometry information\n");
return -1;
}
if (ioctl(fd, BLKGETSIZE64, &part_size)) {
PRINT_PERR("Could not retrieve partition"
" size information\n");
return -1;
}
if (ioctl(fd, BLKSSZGET, &block_size)) {
PRINT_PERR("Could not get blocksize");
return -1;
}
PRINT_TRACE("Partiton %s start: 0x%llx end: 0x%llx\n", path,
(unsigned long long) geo.start * block_size,
(unsigned long long) part_size);
if ((start == geo.start * block_size) && (size == part_size))
return i;
}
return -1;
}
/*
* Read the on-disk zfcpdump boot info and superblock into global variables
*/
static int get_scsi_dump_params(void)
{
uint64_t csum;
int part_num;
if (pread_file(DEV_SCSI, (char *)&mbr, sizeof(mbr), 0) < 0) {
PRINT_ERR("Cannot read MBR\n");
return -1;
}
if (memcmp(&mbr.boot_info.magic, BOOT_INFO_MAGIC,
sizeof(mbr.boot_info.magic))) {
PRINT_ERR("Boot_Info wrong magic\n");
return -1;
}
if (mbr.boot_info.dev_type != BOOT_INFO_DEV_TYPE_SCSI) {
PRINT_ERR("Boot_Info wrong dev type: %d\n",
mbr.boot_info.dev_type);
return -1;
}
if (mbr.boot_info.bp_type != BOOT_INFO_BP_TYPE_DUMP) {
PRINT_ERR("Boot_Info wrong bp type: %d\n",
mbr.boot_info.bp_type);
return -1;
}
if (mbr.boot_info.version != BOOT_INFO_VERSION) {
PRINT_ERR("Boot_Info wrong version: %d\n",
mbr.boot_info.version);
return -1;
}
if (pread_file(DEV_SCSI, (char *)&dump_sb, sizeof(dump_sb),
mbr.boot_info.sb_off) < 0) {
PRINT_ERR("Cannot read superblock\n");
return -1;
}
if (dump_sb.magic != 0x5a46435044554d50ULL) { /* ZFCPDUMP */
PRINT_ERR("Dump data block wrong magic\n");
return -1;
}
part_num = find_part_num(dump_sb.part_start, dump_sb.part_size);
if (part_num < 0) {
PRINT_ERR("Specified dump partition not found\n");
return -1;
}
if (csum_get(dump_sb.part_start + dump_sb.csum_off,
dump_sb.csum_size, &csum)) {
PRINT_ERR("Getting Checksum failed\n");
return -1;
}
if (csum != dump_sb.csum) {
PRINT_ERR("Checksum wrong, filesystem changed\n");
return -1;
}
PRINT(" partition: " DEV_SCSI "%d\n", part_num);
return 0;
}
/*
* Main routine of the zfcpdump tool
*/
int main(int UNUSED(argc), char *UNUSED(argv[]))
{
int rc;
if (zfcpdump_init())
return terminate(1);
PRINT("Dump parameters:\n");
PRINT(" devno....: %s\n", g.dump_devno);
PRINT(" wwpn.....: %s\n", g.dump_wwpn);
PRINT(" lun......: %s\n", g.dump_lun);
PRINT(" conf.....: %s\n", g.dump_bootprog);
if (get_scsi_dump_params())
return terminate(1);
print_newline();
PRINT("Writing dump:\n");
rc = copy_dump("/proc/vmcore", DEV_SCSI,
dump_sb.part_start + dump_sb.dump_off);
return terminate(rc);
}