1 /*
2  * Copyright (c) 2010-2014 Douglas Gilbert.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote products
14  *    derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  */
29 
30 /* This is a C source file contains Windows specific code for the ddpt
31  * utility.
32  */
33 
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37 
38 #ifdef SG_LIB_WIN32
39 
40 #include <unistd.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <ctype.h>
45 #include <errno.h>
46 #include <limits.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <fcntl.h>
50 #define __STDC_FORMAT_MACROS 1
51 #include <inttypes.h>
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <sys/time.h>
55 
56 #include "ddpt.h"       /* includes <signal.h> */
57 
58 #include <windows.h>
59 #include <winioctl.h>
60 
61 #ifndef SG_LIB_MINGW
62 /* cygwin */
63 #include <sys/ioctl.h>
64 #endif
65 
66 #include "sg_lib.h"
67 #include "sg_cmds_basic.h"
68 #include "sg_cmds_extra.h"
69 #include "sg_pt.h"
70 
71 #ifdef HAVE_NANOSLEEP
72 #include <time.h>
73 #elif defined(MSC_VER) || defined(__MINGW32__)
74 #define HAVE_MS_SLEEP
75 #endif
76 
77 
78 #ifndef HAVE_SYSCONF
79 size_t
win32_pagesize(void)80 win32_pagesize(void)
81 {
82     SYSTEM_INFO sys_info;
83 
84     GetSystemInfo(&sys_info);
85     return sys_info.dwPageSize;
86 }
87 #endif
88 
89 void
win32_sleep_ms(int millisecs)90 win32_sleep_ms(int millisecs)
91 {
92     if (millisecs > 0) {
93 #ifdef HAVE_NANOSLEEP
94         struct timespec request;
95 
96         request.tv_sec = millisecs / 1000;
97         request.tv_nsec = (millisecs % 1000) * 1000000;
98         if ((nanosleep(&request, NULL) < 0) && (EINTR != errno))
99             pr2serr("nanosleep: failed, errno=%d\n", errno);
100 #elif defined(HAVE_MS_SLEEP)
101         Sleep(millisecs);
102 #endif
103     }
104 }
105 
106 /* Fetches system error message corresponding to errnum,
107  * placing string in b not exceeding blen bytes. Returns
108  * bytes placed in b (excluding trailing NULL) or -1 for
109  * error. MS refers to them as "System Error Codes". */
110 static const char *
win32_errmsg(int errnum,char * b,int blen)111 win32_errmsg(int errnum, char * b, int blen)
112 {
113     LPTSTR err_txt = 0;
114     DWORD errn = errnum;
115     int len = 0;
116 
117     if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
118                       FORMAT_MESSAGE_FROM_SYSTEM,
119                       NULL,
120                       errn,
121                       0,
122                       (LPTSTR)&err_txt,
123                       0,
124                       NULL) == 0) {
125         snprintf(b, blen, "FormatMessage(errnum=%d) failed", errnum);
126         return b;
127     } else {
128         len = strlen(err_txt);
129         if (len) {
130             if ('\n' == err_txt[len - 1]) {
131                 err_txt[len - 1] = '\0';
132                 if ((len > 1) && ('\r' == err_txt[len - 2]))
133                     err_txt[len - 2] = '\0';
134                 len = strlen(err_txt);
135             }
136         }
137         if (len < 1)
138             b[0] = '\0';
139         else if (len < blen)
140             strcpy(b, err_txt);
141         else {
142             strncpy(b, err_txt, blen);
143             if (blen > 0)
144                 b[blen - 1] = '\0';
145         }
146     }
147     if (err_txt)
148         LocalFree(err_txt);
149     len = strlen(b);
150     snprintf(b + len, blen - len, " [%d]", errnum);
151     return b;
152 }
153 
154 /* Return 1 for filenames starting with '\', or of the form '<letter>:'
155  * or of the form PD<n>, PHYSICALDRIVE<n>, CDROM<n> or TAPE<n>. The <n>
156  * is one or two digits with no following characters. Otherwise return 0. */
157 static int
is_win_blk_dev(const char * fn)158 is_win_blk_dev(const char * fn)
159 {
160     int len, off;
161 
162     len = strlen(fn);
163     if ((2 == len) && isalpha((int)fn[0]) && (':' == fn[1]))
164         return 1;
165     if (len < 3)
166         return 0;
167     if ('\\' == fn[0])
168         return 1;
169     if (0 == strncmp(fn, "PD", 2))
170         off = 2;
171     else if (0 == strncmp(fn, "CDROM", 5))
172         off = 5;
173     else if (0 == strncmp(fn, "PHYSICALDRIVE", 13))
174         off = 13;
175     else if (0 == strncmp(fn, "TAPE", 4))
176         off = 4;
177     else
178         return 0;
179 
180     if (len <= off)
181         return 0;
182     if (! isdigit((int)fn[off]))
183         return 0;
184     if (len == (off + 1))
185         return 1;
186     if ((len != off + 2) || (! isdigit((int)fn[off + 1])))
187         return 0;
188     else
189         return 1;
190 }
191 
192 int
win32_dd_filetype(const char * fn,int verbose)193 win32_dd_filetype(const char * fn, int verbose)
194 {
195     size_t len = strlen(fn);
196 
197     if (verbose) { ; }          /* suppress warning */
198     if ((1 == len) && ('.' == fn[0]))
199         return FT_DEV_NULL;
200     else if ((3 == len) && (
201              (0 == strcmp("NUL", fn)) || (0 == strcmp("nul", fn))))
202         return FT_DEV_NULL;
203     else if ((len > 8) && (0 == strncmp("\\\\.\\TAPE", fn, 8)))
204         return FT_TAPE;
205     else if ((len > 4) && (0 == strncmp("\\\\.\\", fn, 4)))
206         return FT_BLOCK;
207     else
208         return FT_REG;
209 }
210 
211 /* Adjust device file name for Windows; pass-through setup */
212 void
win32_adjust_fns_pt(struct opts_t * op)213 win32_adjust_fns_pt(struct opts_t * op)
214 {
215     char b[INOUTF_SZ];
216     char * fn_arr[2];
217     char * cp;
218     int k, j, len;
219 
220     memset(fn_arr, 0 , sizeof(fn_arr));
221     fn_arr[0] = op->idip->fn;
222     fn_arr[1] = op->odip->fn;
223     for (k = 0; k < 2; ++k) {
224         cp = fn_arr[k];
225         if (NULL == cp)
226             continue;
227         len = strlen(cp);
228         if (len < 2)
229             continue;
230         if ('\\' == cp[0])
231             continue;
232         for (j = 0; j < len; ++j)
233             b[j] = toupper((int)cp[j]);
234         b[len] = '\0';
235         if (is_win_blk_dev(b)) {
236             if (0 == strncmp(b, "PD", 2)) {
237                 strcpy(cp, "\\\\.\\PHYSICALDRIVE");
238                 if (b[2])
239                     strncat(cp, b + 2, len - 2);
240             } else {
241                 strcpy(cp, "\\\\.\\");
242                 strncat(cp, b, len);
243             }
244         }
245     }
246 #ifdef SG_LIB_WIN32_DIRECT
247     if (op->verbose > 4)
248         pr2serr("Initial win32 SPT interface state: %s\n",
249                 scsi_pt_win32_spt_state() ? "direct" : "indirect");
250     scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
251 #endif
252 }
253 
254 /* Main copy loop's read (input) for win32 block device. Returns 0 on
255  * success, else SG_LIB_FILE_ERROR, SG_LIB_CAT_MEDIUM_HARD or -1 . */
256 int
win32_cp_read_block(struct opts_t * op,struct cp_state_t * csp,unsigned char * bp,int * ifull_extrap,int verbose)257 win32_cp_read_block(struct opts_t * op, struct cp_state_t * csp,
258                     unsigned char * bp, int * ifull_extrap, int verbose)
259 {
260     int k, res, res2;
261     int ibs = op->ibs;
262     int64_t offset = op->skip * ibs;
263     int64_t my_skip;
264     int numbytes = csp->icbpt * ibs;
265 
266     if (ifull_extrap)
267         *ifull_extrap = 0;
268     if (offset != csp->if_filepos) {
269         if (verbose > 2)
270             pr2serr("moving if filepos: new_pos=%" PRId64 "\n",
271                     (int64_t)offset);
272         if (win32_set_file_pos(op, DDPT_ARG_IN, offset, verbose))
273             return SG_LIB_FILE_ERROR;
274         csp->if_filepos = offset;
275     }
276     res = win32_block_read(op, bp, numbytes, verbose);
277     if (res < 0) {
278         if ((-SG_LIB_CAT_MEDIUM_HARD == res) && (op->iflagp->coe)) {
279             if (1 == csp->icbpt) {
280                 // Don't read again, this must be bad block
281                 memset(bp, 0, ibs);
282                 if ((res2 = coe_process_eio(op, op->skip)))
283                     return res2;
284                 ++*ifull_extrap;
285                 csp->bytes_read += ibs;
286                 return 0;
287             } else {
288                 my_skip = op->skip;
289                 for (k = 0; k < csp->icbpt;
290                      ++k, ++my_skip, bp += ibs, offset += ibs) {
291                     if (offset != csp->if_filepos) {
292                         if (verbose > 2)
293                             pr2serr("moving if filepos: new_pos=%" PRId64
294                                     "\n", (int64_t)offset);
295                         if (win32_set_file_pos(op, DDPT_ARG_IN, offset,
296                             verbose))
297                             return SG_LIB_FILE_ERROR;
298                         csp->if_filepos = offset;
299                     }
300                     memset(bp, 0, ibs);
301                     res = win32_block_read(op, bp, ibs, verbose);
302                     if (ibs == res) {
303                         zero_coe_limit_count(op);
304                         csp->if_filepos += ibs;
305                         if (verbose > 2)
306                             pr2serr("reading 1 block, skip=%" PRId64 " : "
307                                     "okay\n", my_skip);
308                     } else if (-SG_LIB_CAT_MEDIUM_HARD == res) {
309                         if ((res2 = coe_process_eio(op, my_skip)))
310                             return res2;
311                     } else {
312                         pr2serr("reading 1 block, skip=%" PRId64 " failed\n",
313                                 my_skip);
314                         csp->leave_reason = SG_LIB_CAT_OTHER;
315                         csp->icbpt = k;
316                         csp->ocbpt = (k * ibs) / op->obs;
317                         if (((k * ibs) % op->obs) > 0)
318                             ++csp->ocbpt;
319                         return 0;
320                     }
321                     ++*ifull_extrap;
322                     csp->bytes_read += ibs;
323                 }
324                 return 0;
325             }
326         } else {
327             pr2serr("read(win32_block), skip=%" PRId64 " error occurred\n",
328                     op->skip);
329             return (-SG_LIB_CAT_MEDIUM_HARD == res) ? -res : -1;
330         }
331     } else {
332         if (res < numbytes) {
333             /* assume no partial reads (i.e. non integral blocks) */
334             csp->icbpt = res / ibs;
335             ++csp->leave_after_write;
336             csp->leave_reason = 0; /* assume at end rather than error */
337             csp->ocbpt = res / op->obs;
338             if (verbose > 1)
339                 pr2serr("short read, requested %d blocks, got %d blocks\n",
340                         numbytes / ibs, csp->icbpt);
341         }
342         csp->if_filepos += res;
343         if (ifull_extrap)
344             *ifull_extrap = csp->icbpt;
345     }
346     return 0;
347 }
348 
349 /* Returns 0 on success, 1 on error */
350 int
win32_open_if(struct opts_t * op,int flags,int verbose)351 win32_open_if(struct opts_t * op, int flags, int verbose)
352 {
353     DISK_GEOMETRY g;
354     DWORD count, share_mode, err;
355     char b[80];
356     int blen;
357 
358     blen = sizeof(b);
359     if (verbose)
360         pr2serr("CreateFile(%s , in)\n", op->idip->fn);
361     share_mode = (O_EXCL & flags) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE);
362     op->idip->fh = CreateFile(op->idip->fn,
363                               GENERIC_READ | GENERIC_WRITE,
364                               share_mode,
365                               NULL,
366                               OPEN_EXISTING,
367                               0,
368                               NULL);
369     if (INVALID_HANDLE_VALUE == op->idip->fh) {
370         err = GetLastError();
371         pr2serr("CreateFile(in) failed, %s\n", win32_errmsg(err, b, blen));
372         return 1;
373     }
374     if (0 == DeviceIoControl(op->idip->fh, IOCTL_DISK_GET_DRIVE_GEOMETRY,
375                              NULL, 0, &g, sizeof(g), &count, NULL)) {
376         err = GetLastError();
377         pr2serr("DeviceIoControl(in, geometry) failed, %s\n",
378                 win32_errmsg(err, b, blen));
379         return 1;
380     }
381     if ((int)g.BytesPerSector != op->ibs) {
382         pr2serr("Specified in block size (%d) doesn't match device geometry "
383                 "block size: %d\n", op->ibs, (int)g.BytesPerSector);
384         return 1;
385     }
386     return 0;
387 }
388 
389 /* Returns 0 on success, 1 on error. */
390 int
win32_open_of(struct opts_t * op,int flags,int verbose)391 win32_open_of(struct opts_t * op, int flags, int verbose)
392 {
393     DISK_GEOMETRY g;
394     DWORD count, share_mode, err;
395     char b[80];
396     int blen;
397 
398     blen = sizeof(b);
399     if (verbose)
400         pr2serr("CreateFile(%s , out)\n", op->odip->fn);
401     share_mode = (O_EXCL & flags) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE);
402     op->odip->fh = CreateFile(op->odip->fn,
403                               GENERIC_READ | GENERIC_WRITE,
404                               share_mode,
405                               NULL,
406                               OPEN_EXISTING,
407                               0,
408                               NULL);
409     if (INVALID_HANDLE_VALUE == op->odip->fh) {
410         err = GetLastError();
411         pr2serr("CreateFile(out) failed, %s\n", win32_errmsg(err, b, blen));
412         return 1;
413     }
414     if (0 == DeviceIoControl(op->odip->fh, IOCTL_DISK_GET_DRIVE_GEOMETRY,
415                              NULL, 0, &g, sizeof(g), &count, NULL)) {
416         err = GetLastError();
417         pr2serr("DeviceIoControl(out, geometry) failed, %s\n",
418                 win32_errmsg(err, b, blen));
419         return 1;
420     }
421     if ((int)g.BytesPerSector != op->obs) {
422         pr2serr("Specified out block size (%d) doesn't match device geometry "
423                 "block size: %d\n", op->obs,
424                 (int)g.BytesPerSector);
425         return 1;
426     }
427     return 0;
428 }
429 
430 /* Returns 0 on success, 1 on error */
431 int
win32_set_file_pos(struct opts_t * op,int which_arg,int64_t pos,int verbose)432 win32_set_file_pos(struct opts_t * op, int which_arg, int64_t pos,
433                    int verbose)
434 {
435     LONG lo32 = pos & 0xffffffff;
436     LONG hi32 = (pos >> 32) & 0xffffffff;
437     DWORD err;
438     DWORD lo_ret;
439     HANDLE fh;
440     const char * cp;
441     char b[80];
442     int blen;
443 
444     blen = sizeof(b);
445     fh = (DDPT_ARG_IN == which_arg) ? op->idip->fh : op->odip->fh;
446     cp = (DDPT_ARG_IN == which_arg) ? "in" : "out";
447     if (verbose > 2)
448         pr2serr("SetFilePointer( 0x%" PRIx64 ", %s)\n", pos, cp);
449     lo_ret = SetFilePointer(fh, lo32, &hi32, FILE_BEGIN);
450     if ((INVALID_SET_FILE_POINTER == lo_ret) &&
451         (NO_ERROR != (err = GetLastError()))) {
452         if (verbose)
453             pr2serr("SetFilePointer failed to set pos=[0x%" PRIx64 "], %s\n",
454                     pos, win32_errmsg(err, b, blen));
455         return 1;
456     }
457     return 0;
458 }
459 
460 /* Returns number read, -SG_LIB_CAT_MEDIUM_HARD or -1 on error */
461 int
win32_block_read(struct opts_t * op,unsigned char * bp,int num_bytes,int verbose)462 win32_block_read(struct opts_t * op, unsigned char * bp, int num_bytes,
463                  int verbose)
464 {
465     DWORD num = num_bytes;
466     DWORD howMany, err;
467     char b[80];
468     int blen;
469 
470     blen = sizeof(b);
471     if (verbose > 2)
472         pr2serr("ReadFile(num=%d, in)\n", num_bytes);
473     if (ReadFile(op->idip->fh, bp, num, &howMany, NULL) == 0) {
474         err = GetLastError();
475         if (verbose)
476             pr2serr("ReadFile failed, %s\n", win32_errmsg(err, b, blen));
477         if (23 == err)
478             return -SG_LIB_CAT_MEDIUM_HARD;
479         else
480             return -1;
481     }
482     return (int)howMany;
483 }
484 
485 /* Returns number read from OFILE, -SG_LIB_CAT_MEDIUM_HARD or -1 on error */
486 int
win32_block_read_from_of(struct opts_t * op,unsigned char * bp,int num_bytes,int verbose)487 win32_block_read_from_of(struct opts_t * op, unsigned char * bp,
488                          int num_bytes, int verbose)
489 {
490     DWORD num = num_bytes;
491     DWORD howMany, err;
492     char b[80];
493     int blen;
494 
495     blen = sizeof(b);
496     if (verbose > 2)
497         pr2serr("ReadFile(num=%d, out)\n", num_bytes);
498     if (ReadFile(op->odip->fh, bp, num, &howMany, NULL) == 0) {
499         err = GetLastError();
500         if (verbose)
501             pr2serr("ReadFile(from_of) failed, %s\n",
502                     win32_errmsg(err, b, blen));
503         if (23 == err)
504             return -SG_LIB_CAT_MEDIUM_HARD;
505         else
506             return -1;
507     }
508     return (int)howMany;
509 }
510 
511 /* Returns number written, -SG_LIB_CAT_MEDIUM_HARD or -1 on error */
512 int
win32_block_write(struct opts_t * op,const unsigned char * bp,int num_bytes,int verbose)513 win32_block_write(struct opts_t * op, const unsigned char * bp,
514                   int num_bytes, int verbose)
515 {
516     DWORD num = num_bytes;
517     DWORD howMany, err;
518     char b[80];
519     int blen;
520 
521     blen = sizeof(b);
522     if (verbose > 2)
523         pr2serr("WriteFile(num=%d, out)\n", num_bytes);
524     if (WriteFile(op->odip->fh, bp, num, &howMany, NULL) == 0) {
525         err = GetLastError();
526         if (verbose)
527             pr2serr("WriteFile failed, %s\n", win32_errmsg(err, b, blen));
528         if (23 == err)
529             return -SG_LIB_CAT_MEDIUM_HARD;
530         else
531             return -1;
532     }
533     return (int)howMany;
534 }
535 
536 /* win32_get_blkdev_capacity() returns 0 -> success or -1 -> failure. If
537  * successful writes back sector size (logical block size) using the sect_sz
538  * pointer. Also writes back the number of sectors (logical blocks) on the
539  * block device using num_sect pointer. Win32 version. */
540 int
win32_get_blkdev_capacity(struct opts_t * op,int which_arg,int64_t * num_sect,int * sect_sz)541 win32_get_blkdev_capacity(struct opts_t * op, int which_arg,
542                           int64_t * num_sect, int * sect_sz)
543 {
544     DISK_GEOMETRY g;
545     GET_LENGTH_INFORMATION gli;
546     ULARGE_INTEGER total_bytes;
547     DWORD count, err;
548     HANDLE fh;
549     const char * fname;
550     int64_t byte_len, blks;
551     int fname_len;
552     char dirName[64];
553     char b[80];
554     int blen;
555 
556     blen = sizeof(b);
557     fh = (DDPT_ARG_IN == which_arg) ? op->idip->fh : op->odip->fh;
558     fname = (DDPT_ARG_IN == which_arg) ? op->idip->fn : op->odip->fn;
559     if (op->verbose > 2)
560         pr2serr("win32_get_blkdev_capacity: for %s\n", fname);
561     if (0 == DeviceIoControl(fh, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &g,
562                              sizeof(g), &count, NULL)) {
563         if (op->verbose) {
564             err = GetLastError();
565             pr2serr("DeviceIoControl(blkdev, geometry) failed, %s\n",
566                     win32_errmsg(err, b, blen));
567         }
568         *num_sect = 0;
569         *sect_sz = 0;
570         return -1;
571     }
572     *sect_sz = (int)g.BytesPerSector;
573 
574     /* IOCTL_DISK_GET_LENGTH_INFO not defined before XP */
575     if (DeviceIoControl(fh, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli,
576                         sizeof(gli), &count, NULL)) {
577         byte_len = gli.Length.QuadPart;
578         *num_sect = byte_len / (int)g.BytesPerSector;
579         return 0;
580     } else if (op->verbose > 2) {
581         err = GetLastError();
582         pr2serr("DeviceIoControl(blkdev, length_info) failed, %s\n",
583                 win32_errmsg(err, b, blen));
584     }
585 
586     /* Assume if device name finishes in digit then its physical */
587     fname_len = (int)strlen(fname);
588     if (isdigit((int)fname[fname_len - 1])) {
589         blks = g.Cylinders.QuadPart;
590         blks *= g.TracksPerCylinder;
591         blks *= g.SectorsPerTrack;
592         *num_sect = blks;
593         return 0;
594     }
595     if ((fname_len < 4) || (fname_len > (int)sizeof(dirName))) {
596         pr2serr("win32_get_blkdev_capacity: unable to process %s into "
597                 "directory name\n", fname);
598         *num_sect = 0;
599         return -1;
600     }
601     memcpy(dirName, fname + 4, fname_len - 4);
602     dirName[fname_len - 4] = '\\';
603     dirName[fname_len - 3] = '\0';
604 
605     if (GetDiskFreeSpaceEx(dirName, NULL, &total_bytes, NULL)) {
606         byte_len = total_bytes.QuadPart;
607         *num_sect = byte_len / (int)g.BytesPerSector;
608     } else {
609         if (op->verbose > 1) {
610             err = GetLastError();
611             pr2serr("GetDiskFreeSpaceEx(%s) failed, %s\n", dirName,
612                     win32_errmsg(err, b, blen));
613         }
614         *num_sect = 0;
615         return -1;
616     }
617     return 0;
618 }
619 
620 #endif
621