Files
s390-tools/zfcpdump/zfcpdump_part.c
Alexander Egorenkov b6c30404bc zfcpdump: drop use of mmap() for /proc/vmcore
The function copy_table_entry_write() maps/unmaps /proc/vmcore for each
64kB of the file in order to write its content to a dump partition.
This requires a page allocation of order 2 in kernel (crst_table_alloc)
each time a mmap() syscall is performed and which cannot always be
fulfilled due to external memory fragmentation. And this in turn
results in a OOM kernel panic while writing /proc/vmcore content to
a dump partition. To make zfcpdump more robust in the face of such
problems with page allocations of order > 0, we use a simple file
read/write loop to transfer the content of /proc/vmcore to a dump
partition. This solution is less efficient (by not much) than the old
one with mmap() because it requires additional user space copy but
we trade off here efficiency for robustness which is more important
for zfcpdump.

We use a statically allocated buffer to read data into from /proc/vmcore
and write to a dump partition to avoid repeated use of the memory
mapping kernel path which would be required for a buffer allocated
dynamically at each entry of copy_table_entry_write().

Another possible and very convenient solution would be to use sendfile()
which would avoid any user space copies but, unfortunately, sendfile()
doesn't work with large /proc files.

Example of failed mmap() call for /proc/vmcore
==============================================

  [   26.568654] init invoked oom-killer: gfp_mask=0x40cc0(GFP_KERNEL|__GFP_COMP), order=2, oom_score_adj=0
  [   26.568665] CPU: 0 PID: 1 Comm: init Not tainted 6.9.0-20240504.rc6.git0.9986ea583f39.300.fc39.s390x+zfcpdump #1
  [   26.568668] Hardware name: IBM 3931 A01 704 (LPAR)
  [   26.568669] Call Trace:
  [   26.568671]  [<000003ffe03277c6>] dump_stack_lvl+0x76/0x98
  [   26.568676]  [<000003ffe00f15c8>] dump_header+0x58/0x2d0
  [   26.568680]  [<000003ffe00f219a>] out_of_memory+0x252/0x348
  [   26.568683]  [<000003ffe0132f3e>] __alloc_pages_slowpath.constprop.0+0x65e/0x7c0
  [   26.568686]  [<000003ffe01331a4>] __alloc_pages+0x104/0x128
  [   26.568688]  [<000003ffe001ab46>] crst_table_alloc+0x2e/0xa0
  [   26.568693]  [<000003ffe01164c4>] __pmd_alloc+0x24/0x160
  [   26.568696]  [<000003ffe0116650>] pmd_alloc+0x50/0x68
  [   26.568698]  [<000003ffe0117be4>] remap_pfn_range_notrack+0x194/0x310
  [   26.568701]  [<000003ffe0117d7c>] remap_pfn_range+0x1c/0x28
  [   26.568703]  [<000003ffe0015442>] remap_oldmem_pfn_range+0xb2/0x108
  [   26.568705]  [<000003ffe01a0544>] mmap_vmcore+0x414/0x448
  [   26.568707]  [<000003ffe01937c4>] proc_reg_mmap+0x84/0xa0
  [   26.568709]  [<000003ffe011f264>] mmap_region+0x22c/0x6f8
  [   26.568711]  [<000003ffe011fae8>] do_mmap+0x3b8/0x410
  [   26.568713]  [<000003ffe01015cc>] vm_mmap_pgoff+0x9c/0xe8
  [   26.568715]  [<000003ffe011d006>] ksys_mmap_pgoff+0x8e/0xb8
  [   26.568716]  [<000003ffe011d0f4>] __s390x_sys_old_mmap+0x74/0x90
  [   26.568718]  [<000003ffe0341a2a>] __do_syscall+0x1aa/0x220
  [   26.568720]  [<000003ffe03478b0>] system_call+0x70/0x98
  [   26.568724] Mem-Info:
  [   26.568725] active_anon:1 inactive_anon:12 isolated_anon:0
                  active_file:83590 inactive_file:93939 isolated_file:0
                  unevictable:201 dirty:17845 writeback:4
                  slab_reclaimable:4259 slab_unreclaimable:1482
                  mapped:174 shmem:0 pagetables:810
                  sec_pagetables:0 bounce:0
                  kernel_misc_reclaimable:0
                  free:2694 free_pcp:105 free_cma:0
  [   26.568729] Node 0 active_anon:4kB inactive_anon:48kB active_file:334360kB inactive_file:375756kB unevictable:804kB isolated(anon):0kB isolated(file):0kB mapped:696kB dirty:71380kB writeback:16kB shmem:0kB writeback_tmp:0kB kernel_stack:544kB pagetables:3240kB sec_pagetables:0kB all_unreclaimable? no
  [   26.568732] DMA free:10776kB boost:0kB min:3496kB low:4368kB high:5240kB reserved_highatomic:0KB active_anon:4kB inactive_anon:48kB active_file:334360kB inactive_file:375756kB unevictable:804kB writepending:71360kB present:786428kB managed:764664kB mlocked:0kB bounce:0kB free_pcp:420kB local_pcp:420kB free_cma:0kB
  [   26.568735] lowmem_reserve[]: 0 0 0
  [   26.568738] DMA: 1069*4kB (UH) 757*8kB (UH) 11*16kB (H) 6*32kB (H) 1*64kB (H) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 10764kB
  [   26.568747] 177773 total pagecache pages
  [   26.568748] 196607 pages RAM
  [   26.568748] 0 pages HighMem/MovableOnly
  [   26.568749] 5441 pages reserved
  [   26.568749] Tasks state (memory values in pages):
  [   26.568750] [  pid  ]   uid  tgid total_vm      rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name
  [   26.568751] Out of memory and no killable processes...
  [   26.568752] Kernel panic - not syncing: System is deadlocked on memory
  [   26.568755] CPU: 0 PID: 1 Comm: init Not tainted 6.9.0-20240504.rc6.git0.9986ea583f39.300.fc39.s390x+zfcpdump #1
  [   26.568758] Hardware name: IBM 3931 A01 704 (LPAR)
  [   26.568759] Call Trace:
  [   26.568760]  [<000003ffe03277c6>] dump_stack_lvl+0x76/0x98
  [   26.568763]  [<000003ffe001f14e>] panic+0x10e/0x2e8
  [   26.568765]  [<000003ffe00f21c8>] out_of_memory+0x280/0x348
  [   26.568768]  [<000003ffe0132f3e>] __alloc_pages_slowpath.constprop.0+0x65e/0x7c0
  [   26.568771]  [<000003ffe01331a4>] __alloc_pages+0x104/0x128
  [   26.568773]  [<000003ffe001ab46>] crst_table_alloc+0x2e/0xa0
  [   26.568776]  [<000003ffe01164c4>] __pmd_alloc+0x24/0x160
  [   26.568779]  [<000003ffe0116650>] pmd_alloc+0x50/0x68
  [   26.568781]  [<000003ffe0117be4>] remap_pfn_range_notrack+0x194/0x310
  [   26.568784]  [<000003ffe0117d7c>] remap_pfn_range+0x1c/0x28
  [   26.568787]  [<000003ffe0015442>] remap_oldmem_pfn_range+0xb2/0x108
  [   26.568789]  [<000003ffe01a0544>] mmap_vmcore+0x414/0x448
  [   26.568791]  [<000003ffe01937c4>] proc_reg_mmap+0x84/0xa0
  [   26.568793]  [<000003ffe011f264>] mmap_region+0x22c/0x6f8
  [   26.568795]  [<000003ffe011fae8>] do_mmap+0x3b8/0x410
  [   26.568797]  [<000003ffe01015cc>] vm_mmap_pgoff+0x9c/0xe8
  [   26.568799]  [<000003ffe011d006>] ksys_mmap_pgoff+0x8e/0xb8
  [   26.568801]  [<000003ffe011d0f4>] __s390x_sys_old_mmap+0x74/0x90
  [   26.568803]  [<000003ffe0341a2a>] __do_syscall+0x1aa/0x220
  [   26.568805]  [<000003ffe03478b0>] system_call+0x70/0x98

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2024-06-24 16:23:24 +02:00

518 lines
14 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 <stdbool.h>
#include <string.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 "boot/boot_defs.h"
#include "zfcpdump.h"
#define COPY_BUF_SIZE 0x10000UL
/*
* Copy table entry
*/
struct copy_table_entry {
unsigned long size;
unsigned long off;
bool hsa;
};
struct copy_table {
int cnt;
int max;
struct copy_table_entry *entry;
};
/*
* 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, SCSI_DUMP_SB_SEED);
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_offset,
dump_sb.csum_size, &dump_sb.csum)) {
PRINT_ERR("Get check sum failed\n");
return -1;
}
if (lseek(fd, mbr.boot_info.bp.dump.param.scsi.block, 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;
}
static int cmp_ct_entries(const void *_entry1, const void *_entry2)
{
const struct copy_table_entry *entry1 = (const struct copy_table_entry *)_entry1;
const struct copy_table_entry *entry2 = (const struct copy_table_entry *)_entry2;
/* Sort copy table entries by their file offset in ascending order */
if (entry1->off <= entry2->off)
return -1;
else if (entry1->off > entry2->off)
return 1;
else
return 0;
}
static inline void copy_table_add_entry(struct copy_table *table, unsigned long off,
unsigned long size, bool hsa)
{
const int i = table->cnt;
table->entry[i].off = off;
table->entry[i].size = size;
table->entry[i].hsa = hsa;
table->cnt++;
}
static void copy_table_add_non_hsa_file_regions(struct copy_table *table)
{
const int hsa_entry_count = table->cnt;
unsigned long off, size;
int i;
/*
* We write the front page of /proc/vmcore at the end of the dump processing.
* This ensures that the dump stays invalid until all data
* is written. It is guaranteed that a copy table entry for file offset 0
* never covers HSA memory and at least of size of a single page because
* HSA memory is always page aligned.
*/
off = PAGE_SIZE;
size = table->entry[0].off - off;
if (size > 0)
copy_table_add_entry(table, off, size, false);
/*
* Add copy table entries covering non-HSA file regions located before
* each copy table entry covering a HSA file region. Start with the second
* HSA copy table entry.
*/
for (i = 1; i < hsa_entry_count; i++) {
off = table->entry[i - 1].off + table->entry[i - 1].size;
size = table->entry[i].off - off;
if (size > 0)
copy_table_add_entry(table, off, size, false);
}
/*
* Add a copy table entry that covers the end of /proc/vmcore which is
* not covered by a copy table entry for HSA.
*/
i = hsa_entry_count - 1;
off = table->entry[i].off + table->entry[i].size;
if (off < g.vmcore_size) {
size = g.vmcore_size - off;
copy_table_add_entry(table, off, size, false);
}
/*
* Add a copy table entry covering the front page of /proc/vmcore which
* is not covered by HSA as the last entry.
*/
copy_table_add_entry(table, 0, PAGE_SIZE, false);
}
static int copy_table_init(int fd, struct copy_table *table)
{
const unsigned long hsa_size = get_hsa_size();
unsigned long off, size;
int i, max_table_size;
Elf64_Ehdr ehdr;
Elf64_Phdr phdr;
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;
}
/*
* Each ELF LOAD segment may contain at most one HSA file region which will
* result in exactly one HSA copy table entry. Furthermore, this will result
* in at most 1 extra non-HSA copy table entry preceding the HSA copy
* table entry apart from the first and the last HSA copy table entries
* which will result in 2 non-HSA copy table entries.
*
* /proc/vmcore
* -------------------------------------------------------------------
* | page sized | non-HSA | HSA | non-HSA | HSA | non-HSA |
* | non-HSA | region 2 | region 1 | region 3 | region 2 | region 4 |
* | region 1 | | | | | |
* -------------------------------------------------------------------
*/
table->cnt = 0;
table->max = ehdr.e_phnum * 2 + 2;
max_table_size = table->max * sizeof(struct copy_table_entry);
table->entry = malloc(max_table_size);
if (!table->entry) {
PRINT_ERR("Memory allocation of %d byte(s) failed\n", max_table_size);
return -1;
}
/*
* First add all HSA file regions to copy table.
* Each ELF LOAD segment may contain at most one HSA segment.
*/
for (i = 0; i < ehdr.e_phnum; i++) {
if (read(fd, &phdr, sizeof(phdr)) < 0)
return -1;
if (phdr.p_type != PT_LOAD)
continue;
PRINT_TRACE("ELF LOAD segment: p_offset=0x%016lx p_filesz=0x%016lx p_paddr=0x%016lx p_vaddr=0x%016lx\n",
phdr.p_offset, phdr.p_filesz, phdr.p_paddr, phdr.p_vaddr);
if (phdr.p_paddr >= hsa_size)
continue;
off = phdr.p_offset;
size = MIN(phdr.p_filesz, hsa_size - phdr.p_paddr);
if (size > 0)
copy_table_add_entry(table, off, size, true);
}
if (table->cnt == 0) {
PRINT_ERR("Could not find ELF LOAD segments containing HSA\n");
return -1;
}
/* Sort all HSA copy table entries by their file offset */
qsort(table->entry, table->cnt, sizeof(struct copy_table_entry), cmp_ct_entries);
/*
* Add copy table entries which cover all of /proc/vmcore not covered
* by HSA copy table entries added above.
*/
copy_table_add_non_hsa_file_regions(table);
return 0;
}
/*
* Copy one copy table entry form /proc/vmcore to dump partition
*/
static int copy_table_entry_write(int fdin, int fdout,
const struct copy_table_entry *entry,
unsigned long disk_offset)
{
static unsigned char buf[COPY_BUF_SIZE];
unsigned long bytes_left, count;
ssize_t bytes_read, bytes_written;
bytes_left = entry->size;
if (bytes_left == 0)
return 0;
if (lseek(fdin, entry->off, SEEK_SET) < 0)
return -1;
if (lseek(fdout, disk_offset + entry->off, SEEK_SET) < 0)
return -1;
PRINT_TRACE("Write dump: vmcore offset=0x%016lx disk offset=0x%016lx bytes=0x%016lx\n",
entry->off, disk_offset + entry->off, bytes_left);
while (bytes_left > 0) {
count = MIN(COPY_BUF_SIZE, bytes_left);
bytes_read = read(fdin, buf, count);
if (bytes_read < 0) {
PRINT_PERR("Read from /proc/vmcore failed: offset=0x%016lx bytes=0x%016lx\n",
entry->off + entry->size - bytes_left, count);
return -1;
}
bytes_written = write(fdout, buf, bytes_read);
if (bytes_written < 0) {
PRINT_PERR("Write to partition failed: offset=0x%016lx bytes=0x%016lx\n",
disk_offset + entry->off + entry->size - bytes_left,
bytes_read);
return -1;
}
bytes_left -= bytes_written;
show_progress(bytes_written);
}
return 0;
}
static int copy_dump(const char *in, const char *out, unsigned long offset)
{
struct copy_table table = { 0 };
char busy_str[] = "zfcpdump busy";
int fdout, fdin, i, rc = -1, hsa_released = 0;
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 < table.cnt; i++) {
PRINT_TRACE("Write copy table entry %d: off=0x%016lx size=0x%016lx hsa=%d\n",
i, table.entry[i].off, table.entry[i].size, table.entry[i].hsa ? 1 : 0);
if (!hsa_released && !table.entry[i].hsa) {
/*
* First encountered non-HSA copy table entry guarantees
* that no more HSA memory copy table entries will appear
* and, therefore, HSA memory can be finally released.
*/
PRINT_TRACE("Release HSA memory\n");
release_hsa();
hsa_released = 1;
}
if (copy_table_entry_write(fdin, fdout, &table.entry[i], offset))
goto out_close_fdout;
}
rc = 0;
out_close_fdout:
if (csum_update(fdout))
rc = -1;
fsync(fdout);
close(fdout);
free(table.entry);
out_close_fdin:
if (!hsa_released)
release_hsa();
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.bp.dump.param.scsi.block) < 0) {
PRINT_ERR("Cannot read superblock\n");
return -1;
}
if (dump_sb.magic != SCSI_DUMP_SB_MAGIC) {
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_offset,
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_offset);
return terminate(rc);
}