Logo Search packages:      
Sourcecode: afflib version File versions  Download package

imager.cpp

#include "afflib.h"
#include "afflib_i.h"
#include "imager.h"
#include "aimage.h"
#include "ident.h"
#include "gui.h"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <term.h>
#include <assert.h>
#include <errno.h>

/*
 * imager.cpp:
 * The C++ imaging library.
 */

imager::imager()
{
    allow_regular = false;
    total_segments_written = 0;
    total_sectors_written = 0;
    total_sectors_read=0;
    total_bytes_read = 0;
    total_bytes_written = 0;
    total_blank_sectors = 0;
    
    callback_bytes_to_write = 0;
    callback_bytes_written = 0;

    imaging = false;
    imaging_failed = false;

    opt_logAFF = false;
    logfile = 0;

    last_sector_read = 0;     // sector number
    bad_sectors_read = 0;
    af = 0;
    fout = 0;                       // 
    hash_invalid = false;           // make true to avoid hash calculation

    memset(cmd_attach,0,sizeof(cmd_attach));
    memset(cmd_detach,0,sizeof(cmd_detach));

    scsi_bus = -1;
    scsi_tid = -1;
    scsi_lun = -1;
    scsi_pass = -1;
    ata_dev = -1;
    
    memset(device_model,0,sizeof(device_model));
    memset(serial_number,0,sizeof(serial_number));
    memset(firmware_revision,0,sizeof(firmware_revision));

    in     = -1;
    in_pos = 0;
    sector_size = 0;
    total_sectors = 0;
    maxreadblocks = 0;

    af = 0;
    fout = 0;

    memset(fname_raw,0,sizeof(fname_raw));
    memset(fname_aff,0,sizeof(fname_aff));
    memset(infile,0,sizeof(infile));

    memset(final_md5,0,sizeof(final_md5));
    memset(final_sha1,0,sizeof(final_sha1));
    hash_invalid = false;

    last_sector_read = 0;
    last_sectors_read = 0;

    seek_on_output = false;
    retry_count = 0;

    buf = 0;
    bufsize = 512;                  // good guess
    memset(blank_sector,0,sizeof(blank_sector));
    partial_sector_left  = 0;
    partial_sector_blank = false;

    bad_sectors_read = 0;
    consecutive_read_errors = 0;
    consecutive_read_error_regions = 0;
    error_recovery_phase = 0;
    last_direction = 0;

    /* error recovery */
}


void imager::write_data(unsigned char *buf,uint64 offset,int len)
{
    /* if this is supposed to be bad data, make sure that it is properly bad... */
    if(opt_debug==99){
      printf("imager::write_data(buf/x=%p,offset=%qd len=%d buf=%s\n",buf,offset,len,buf);
      if(offset%sector_size != 0){
          err(1,"huh? offset mod %d = %d\n",sector_size,(int)offset%sector_size);
      }
    }

    if(!hash_invalid){
      /* Update hash functions. */
      MD5_Update(&md5,buf,len);
      SHA1_Update(&sha,buf,len);
    }

    /* Count the number of blank sectors.
     */
    /* First, see if there is a partial blank sector that we are still processing... */
    int len_left = len;
    while(len_left>0 && partial_sector_left>0){
      if(buf[len-len_left] != 0){
          partial_sector_blank = false; // it's no longer blank
      }
      len_left--;
      partial_sector_left--;
    }
    if(partial_sector_left==0){ // we reached the end of the partial sector
      if(partial_sector_blank==true){     // and the sector is blank!
          total_blank_sectors++;
      }
    }
    /* Is it possible to look for full sectors? */
    while(len_left > sector_size){
      if(memcmp(buf+(len-len_left),blank_sector,sector_size)==0){
          total_blank_sectors++;
      }
      len_left -= sector_size;
    }

    if(partial_sector_left==0 && len_left>0){ // some left, so we have a new partial sector
      partial_sector_left  = sector_size;
      partial_sector_blank = true;
    }

    /* If anything is left, do the partial sector */
    while(len_left>0){
      if(buf[len-len_left] != 0){
          partial_sector_blank = false; // no longer blank
      }
      len_left--;
      partial_sector_left--;
    }


    /* Write it out and carry on... */
    if(af){
      af_seek(af,offset,SEEK_SET);
      if(af_write(af,buf,len)!=len){
          perror("af_write"); // this is bad
          af_close(af); // try to gracefully recover
          fprintf(stderr,"\r\n");
          fprintf(stderr,"Imaging terminated because af_write failed.\n\r");
          exit(1);
      }
    }
    if(fout){
      fseeko(fout,offset,SEEK_SET);
      if(fwrite(buf,1,len,fout)!=(unsigned)len){
          perror("fwrite");   // this is also bad
          fclose(fout); // get what we can out to disk
          fprintf(stderr,"Imaging terminated because fwrite() failed.\n");
          fprintf(stderr,"Correct error condition and re-run aimage.\n");
          fprintf(stderr,"You may be able to carry on from where you left off.\n");
          exit(1);
      }
    }
    total_sectors_written += len/sector_size;
    total_bytes_written   += len;
}

void imager::status()
{
    if(opt_quiet==0 && opt_silent==0){
      my_refresh(this,0);                 // just refresh; most status is done by AFF callback
    }
}

/****************************************************************
 *** isleep(): An informative sleep...                        ***
 ****************************************************************/
void isleep(int s)
{
  printf("isleep %d\n",s);
  for(int i=0;i<s;i++){
    printf("\rSleeping for %d seconds; %d left...",s,s-i);
    fflush(stdout);
    sleep(1);
  }
  printf("\r%50s\r","");
  fflush(stdout);
}



/*
 * open_dev(char outdev[MAXPATHLEN],char *indev)
 * Try to open the friendly device name.
 * If successful, return the actual device in outdev and the FD.
 * 
 * If the device can be detected but not mounted (common with some
 * broken IDE drives), return fd==65536 (FD_IDENT). 
 * This says that we can't open it, but should ident it.
 */


int  imager::open_dev(const char *friendly_name)
{
    /****************************************************************
     *** Check for ata%d or ide%d
     ****************************************************************/

    ata_dev = -1;
    sscanf(friendly_name,"ata%d",&ata_dev);     // try to find ata0
    if(ata_dev==-1){
      sscanf(friendly_name,"ide%d",&ata_dev);   // try to find ide0
    }

    if(ata_dev != -1){              // if we found the device
      /* Create the attach and detach commands */

      char dev0[64];                // space for the first channel
      char dev1[64];                // space for the second channel
      char *dev[2] = {dev0,dev1};

      make_ata_attach_commands(cmd_attach,cmd_detach,dev0,dev1,ata_dev);
      system(cmd_detach);     // make sure we are detached first

      int i;
      for(i=0;i<10;i++){
          int delay = i*3;
          printf("\nOpening special ATA Bus #%d...\n",ata_dev);
          if(i>0){
            printf("Attempt %d out of %d.\n",i+1,10);
          }
          printf("# %s\n",cmd_attach);
          system(cmd_attach);
          
          /* See if we found the device */
          for(int d=0;d<2;d++){
            if(access(dev[d],F_OK)==0){
                if(access(dev[d],R_OK)){
                  // don't have permission to read it.
                  // this is bad
                  err(1,dev[d]);
                }
                if(delay){
                  printf("Waiting %d second%s for %s to spin up...\n",
                         delay,delay==1?"":"s",infile);  
                  isleep(delay);
                } 
                strcpy(infile,dev[d]); // we will try this one
                int fd = open(infile,O_RDONLY);
                if(fd>0){
                  /* The device was successfully opened. */
                  return fd;        // got it!
                }
                perror(infile);           // give a warning
            }
          }
          printf("Detaching device and trying again...\n");
          printf("# %s\n",cmd_detach);
          system(cmd_detach);
          isleep(delay);
      }
      /* Been through too many times. Did we get a device?
       * If so, just ident it...
       */
      if(infile[0]){
          imaging_failed = true;
          return FD_IDENT;
      }
    }

    /****************************************************************
     *** Check for scsi%d
     *** In our testing with FreeBSD, there is no advantage to repeatedly
     *** attempting to attach or detach...
     ****************************************************************/
    if(sscanf(friendly_name,"scsi%d",&scsi_bus)==1){
      if(scsi_attach(infile,sizeof(infile),this)==0){
          int fd = open(infile,O_RDONLY);
          if(fd>0){
            return fd;
          }
          /* attach was successful but open failed. */
          imaging_failed = true;
          return FD_IDENT;
      }
    }
    return -1;
}





/*
 * main image loop.
 * if high_water_mark==0, then we do not know how many blocks the input
 * is; just read it byte-by-byte...
 */
void imager::image_loop(uint64 low_water_mark, // sector # to start
                  uint64 high_water_mark, // sector # to end
                  int direction, int readsectors,int error_mask)
{
    // buffer to store the data we read
    bufsize = readsectors*sector_size;
    buf = (unsigned char *)malloc(bufsize); 
    memset(buf,0,sizeof(buf));
    uint64 data_offset = 0;         // offset into output file
    bool valid_reverse_data = false;            // did we ever get valid data in the reverse direction?
    bool last_read_short = false;

    if(!buf) err(1,"malloc");

    /* Loop as long as we have room, or until we get an EOF
     * (if high_water_mark is 0.)
     */
    imaging = true;
    while(low_water_mark < high_water_mark || high_water_mark==0){ 

      /* Figure out where to read and how how many sectors to read */
      uint64 snum;      // where we will be reading
      unsigned int sectors_to_read = readsectors;
      if(sectors_to_read > maxreadblocks && maxreadblocks>0){
          sectors_to_read = maxreadblocks;
      }
      if(direction==1){ // going up
          snum = low_water_mark;

          /* If a high water mark is set, take it into account */
          if(high_water_mark>0){
            unsigned int sectors_left = high_water_mark - snum;
            if(sectors_left < sectors_to_read){ 
                sectors_to_read = sectors_left;
            }
          }
      }
      else {
          assert(high_water_mark != 0); // we can't go backwards if we don't know end

          snum = high_water_mark - sectors_to_read;
          if(snum<low_water_mark){
            snum = low_water_mark;
            sectors_to_read = high_water_mark - low_water_mark;
          }
      }

      last_sector_read = snum;
      last_sectors_read = sectors_to_read;
      last_direction = direction;

      data_offset = sector_size * snum; // where we want to start reading

      if(data_offset != in_pos){    // eliminate unnecessary seeks
          lseek(in,data_offset,SEEK_SET); // make sure we are at the right place; (ignore error)
          in_pos = data_offset;
      }

      status();               // tell the user what we are doing

      int bytes_to_read = sectors_to_read * sector_size;

      /* Fill the buffer that we are going to read with the bad flag */
      for(int i=0;i<bytes_to_read;i+=af->image_sectorsize){
          memcpy(buf+i,af_badflag(af),af->image_sectorsize);
      }

      /* Now seek and read */

      int bytes_read    = 0;
      if(opt_debug==99){
          bytes_read = -1; // simulate a read error
      } else {
          if(opt_use_timers) read_timer.start();
          bytes_read = read(in,buf,bytes_to_read);
          if(opt_use_timers) read_timer.stop();
      }
      if(bytes_read>=0){
          in_pos += bytes_read;     // update position
      }

      /* Note if we got valid data in the reverse direction */
      if((direction == -1) && (bytes_read>0)) valid_reverse_data = true;


      if(bytes_read == bytes_to_read){
          /* Got a good read! */
          total_sectors_read    += sectors_to_read;
          total_bytes_read      += bytes_read;

          /* Reset the error counters */
          consecutive_read_errors = 0;
          consecutive_read_error_regions = 0;
          last_read_short = false;

          /* Write the data! */
          write_data(buf,data_offset,bytes_read);

          if(direction==1){
            low_water_mark += sectors_to_read;
          }
          else {
            high_water_mark -= sectors_to_read;
          }
          continue;
      }

      /* Some kind of error... */

      /* If high water mark is 0,
       * then just write out what we read and continue, because we don't know how many
       * bytes we can read...
       */
      if(high_water_mark==0 && bytes_read<=0){
          break;  // end of pipe/file/whatever
      }

      /* If we are reading forward and we got an incomplete read, just live with it... */
      if(direction==1 && bytes_read>0){
          total_bytes_read      += bytes_read; 
          write_data(buf,data_offset,bytes_read);
          data_offset += bytes_read;      // move along
          low_water_mark += bytes_read/sector_size;
          last_read_short = true;
          continue;
      }

      /* Error handling follows. This code will automatically retry
       * the same set of sectors retry_count and then switch direction.
       */
      if(error_mask==0){
          /* If errors on this attempted read exceed the threshold,
           * just note how many bytes we were able to read and swap directions if necessary.
           * If we have done that too many times in a row, then give up...
           */
          if(++consecutive_read_errors>retry_count){
            consecutive_read_errors=0; // reset the counter

            /* If we got an error, note it --- unless one of two conditions are true:
             * we are going forwards and the last was a short read.
             * we are going backwards and we have never gotten valid data going backwards.
             */
            if(((direction==1) && (last_read_short==false)) ||
               ((direction==-1) && (valid_reverse_data==true))){
                write_data(buf,data_offset,bytes_to_read);
                bad_sectors_read += sectors_to_read; // I'm giving up on them...
                hash_invalid = true;
            }
            
            if(++consecutive_read_error_regions<retry_count){ //
                /* Just skip to the next area */
                int sectors_to_bump = readsectors / 2;
                if(sectors_to_bump==0) sectors_to_bump = 1; // need to bump by a positive amount
                
                /* Is there room left? */
                if(low_water_mark + sectors_to_bump > high_water_mark){
                  break;            // no more room.
                }
                
                if(direction == 1){
                  low_water_mark += sectors_to_bump;  // give a little bump
                }
                else {
                  high_water_mark -= sectors_to_bump;
                }
            }
            else {
                /* Retry count in this directory exceeded. Either reverse
                 * direction or give up...
                 */
                if(direction ==  1){
                  consecutive_read_errors = 0; // reset count
                  consecutive_read_error_regions = 0;
                  direction = -1;
                  continue;
                }
                if(direction == -1){
                  /* That's it. Give up */
                  break;
                }
                errx(1,"imager: Unknown direction: %d\n",direction);
            }
          }
      }
      if(error_mask==1){
          /* Stop reading at the first error and write the incomplete buffer */
          if(bytes_read>0){
            write_data(buf,data_offset,bytes_read);
          }
          break;
      }
    }
    imaging = false;
    free(buf);
    buf = 0;                        // no longer valid
}

/* Returns 0 if okay, -1 if failure. */
int imager::set_input_fd(int ifd)
{
    in = ifd;

    /* Make sure infile is actually a device, and not a file */
    struct stat so;
    if(fstat(ifd,&so)){
      perror("fstat");
      return -1;
    }
    int mode = so.st_mode & S_IFMT;
    struct af_figure_media_buf afb;
    memset(&afb,0,sizeof(afb));

    /* Now figure out how many input blocks we have */
    if(mode==S_IFBLK || mode==S_IFCHR){
      if (af_figure_media(in,&afb)){
          return -1;
      }
      sector_size = afb.sector_size;
      total_sectors = afb.total_sectors;
      maxreadblocks = afb.max_read_blocks;
      return 0;
    }
    if(mode==S_IFREG){              // regular file
      if(allow_regular==false){
          fprintf(stderr,"input is a regular file.\n");
          fprintf(stderr,"Use afconvert or aimage -E to convert regular files to AFF.\n");
          return -1;
      }
      /* Just got with the file size... */
      sector_size  = 512;           // default
      total_sectors= so.st_size / sector_size;
      maxreadblocks = 0;
      return 0;
    }

    /* Okay. We don't know how big it will be, so just get what we can... */
    sector_size   = 512;            // it's a good guess
    total_sectors = 0;              // we don't know
    maxreadblocks = 0;              // no limit
    return 0;
}


int imager::set_input(const char *name)
{
    /* Set the input given a "name"
     * 
     * First, try to open the input file.
     * If the name specified by the user can be opened, use it.
     * If the name cannot be opened work, see if it is a operating system
     * specific filename such as "ide0" or "ata0", in which the
     * operating system-specific code will attempt to attach the
     * device.
     */
    /* Check for '-' which is stdin */
    if(strcmp(name,"-")==0){
      strcpy(infile,name);          // make a local copy
      return set_input_fd(0);             // file descriptor 0 is stdin
    }

    /* Check for 'listen:%d' which means listen for a TCP connection */
    int port;
    if(sscanf(name,"listen:%d",&port)==1){
      socket_listen(port);          // sets infile
      return 0;
    }

    /* The name must be a file. See if we can open it... */
    int ifd = open(name,O_RDONLY);
    if(ifd>0){
      strcpy(infile,name);          // make a local copy
      return set_input_fd(ifd);
    }

    /* Attempt to open infile failed; check for a special
     * device name...
     */
    ifd = open_dev(name);
    if(ifd>0){
      return set_input_fd(ifd);
    }

    /* If we haven't been able to open the something by this point, give up. */
    perror(name);
    return -1;
}



void imager::hash_setup()
{
    /* Set up the MD5 & SHA1 machinery */
    MD5_Init(&md5);
    SHA1_Init(&sha);
}


/* start_imaging2():
 * Actually run the imaging
 */
void imager::start_imaging2()
{
    retry_count = opt_retry_count;

    /* See if the skipping makes sense */
    if(!opt_skip_sectors){
      if(opt_skip % sector_size != 0){
          fprintf(stderr,
                "Skipping must be an integral multiple of sector size "
                "(%d bytes)\n",sector_size);
          imaging_failed = true;
          return;
      }
      opt_skip /= sector_size;      // get the actuall offset
    }

    int starting_direction = 1;
    if(opt_reverse) starting_direction = -1;

    /****************************************************************
     *** Start imaging
     ****************************************************************/

    signal(SIGINT,sig_intr);  // set the signal handler
    hash_setup();       // get ready...
    image_loop(opt_skip,
             total_sectors,starting_direction,
             opt_readsectors,opt_error_count); // start the process
    signal(SIGINT,0);         // unset the handler


    /****************************************************************
     *** Finished imaging
     ****************************************************************/

    /* Calculate the final MD5 and SHA1 */
    MD5_Final(final_md5,&md5);
    SHA1_Final(final_sha1,&sha);
}




/* Start the imaging.
 * If files are specified, opens them.
 * then does the imaging.
 * Then closes the files.
 */
int imager::start_imaging()
{
    output_ident = new class ident(fname_aff[0] ? fname_aff : fname_raw);

    /* If the user is imaging to an AFF file,
     * open it and try to ident the drive.
     * Drive ident is not done if writing to a raw file, because
     * there is no place to store the ident information. This will be changed
     * when we can write an XML log.
     */
    if(af){
      af->tag = (void *)this;             // remember me!
      if(opt_logAFF){
          af->logfile = logfile;
      }

      /* If the segment size hasn't been set, then set it */
      ident();                // ident the drive if possible
      af_update_seg(af,AF_ACQUISITION_COMMAND_LINE,0,command_line,strlen(command_line),1);
      af_update_seg(af,AF_ACQUISITION_DEVICE,0,infile,strlen(infile),1);
      af_set_sectorsize(af,sector_size);
      af_set_pagesize(af,opt_pagesize);   // sets current page size
      if(opt_maxsize) af_set_maxsize(af,opt_maxsize);
      if(total_sectors>0){
          af_update_segq(af,AF_DEVICE_SECTORS,(int64)total_sectors,1);
      }
      af_set_callback(af,segwrite_callback);

      if(opt_no_ifconfig==0){
          char *macs = ident::mac_addresses();
          if(macs){
            af_update_seg(af,AF_ACQUISITION_MACADDR,0,macs,strlen(macs),1);
            free(macs);
          }
      }

      if(opt_no_dmesg==0){
          char *dmesg = ident::dmesg();
          if(dmesg && strlen(dmesg)){
            af_update_seg(af,AF_ACQUISITION_DMESG,0,dmesg,strlen(dmesg),1);
            free(dmesg);
          }
      }

      unsigned char bit128[16];
      RAND_pseudo_bytes(bit128,sizeof(bit128));
      af_update_seg(af,AF_IMAGE_GID,0,bit128,sizeof(bit128),1);

      time_t t = time(0);
      char timebuf[64];
      strftime(timebuf,sizeof(timebuf),"%Y-%m-%d %H:%M:%S",localtime(&t));
      af_update_seg(af,AF_ACQUISITION_DATE,0,timebuf,strlen(timebuf),1);
    }

    /* Here is where the imaging takes place.
     * Do it unless ifd==FD_IDENT, which is the fictitious FD.
     */

    if(logfile){
      fprintf(logfile,"aimage infile=%s ",infile);
      if(fname_raw[0]) fprintf(logfile,"outfile_raw=%s ",fname_raw);
      if(fname_aff[0]) fprintf(logfile,"outfile_aff=%s ",fname_aff);
      fprintf(logfile,"\n");
    }
    if(in!=FD_IDENT){
      imaging_timer.start();
      start_imaging2();
      imaging_timer.stop();
    }


    /* AFF Cleanup... */
    if(af){
      if(hash_invalid==false){
          if(af_update_seg(af,AF_MD5,0,final_md5,16,1)){
            if(errno!=ENOTSUP) perror("Could not update AF_MD5");
          }
          if(af_update_seg(af,AF_SHA1,0,final_sha1,20,1)){
            if(errno!=ENOTSUP) perror("Could not update AF_SHA1");
          }
      }
      else {
          af_del_seg(af,AF_MD5);    // because it is not valid
          af_del_seg(af,AF_SHA1);
      }
      if(af_update_segq(af,AF_BADSECTORS, (int64)bad_sectors_read,1)){
          if(errno!=ENOTSUP) perror("Could not update AF_BADSECTORS");
      }
      if(af_update_segq(af,AF_BLANKSECTORS, (int64)total_blank_sectors,1)){
          if(errno!=ENOTSUP) perror("Could not update AF_BLANKSECTORS");
      }
      unsigned long elapsed_seconds = (unsigned long)imaging_timer.elapsed_seconds();
      if(af_update_seg(af,AF_ACQUISITION_SECONDS,elapsed_seconds,0,0,1)){
          if(errno!=ENOTSUP) perror("Could not update AF_ACQUISITION_SECONDS");
      }
    }
    return 0;
}



/* Listen for a local socket connection and return the
 * file descriptor...
 */
int imager::socket_listen(int port)
{
    struct sockaddr_in local;
    struct sockaddr_in remote;
    socklen_t rsize = sizeof(remote);

    int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);    /* Open a listening socket ... */
    memset(&local,0,sizeof(local));
    memset(&remote,0,sizeof(remote));
#ifndef linux                       // have I mentioned how much I hate linux?
    local.sin_len = sizeof(sockaddr_in);
#endif
    local.sin_family = AF_INET;
    local.sin_port   = htonl(port); // listen on port 5555
    if(bind(sock,(sockaddr *)&local,sizeof(local))) err(1,"bind");
    if(listen(sock,0)) err(1,"listen");         // listen, and only accept one
    printf("Listening for connection on port %d...\n",port);
    in = accept(sock,(sockaddr *)&remote,&rsize);
    if(in<0){
      perror("accept");
      in = 0;
      return -1;
    }
    strcpy(infile,inet_ntoa(remote.sin_addr));
    printf("Connection accepted from %s\n",infile);
    return 0;
}



/* final_report():
 * Let's make the user feel good...
 */
void imager::final_report()
{
    bold("****************************** IMAGING REPORT ******************************");
    putchar('\n');
    printf("Input: "); bold(infile); putchar('\n');
    if(device_model[0]){
      printf("  Model: ");
      bold(device_model);
    }
    if(serial_number[0]){
      printf("  S/N: ");
      bold(serial_number);
    }
    putchar('\n');
    if(fname_aff[0]){
      printf("  AFF Output file: ");
      bold(fname_aff);
    }
    if(fname_raw[0]){
      printf("   Raw Output file: ");
      bold(fname_raw);
    }

    putchar('\n');
    
    char buf[64];
    make_commas(total_bytes_read,buf);
    printf("  Bytes read: %s\n",buf);

    make_commas(total_bytes_written,buf);
    printf("  Bytes written: %s\n",buf);
    
    char print_buf[256];
    printf("\n");
    if(hash_invalid==false){
      printf("raw image md5:  %s\n",
             af_hexbuf(print_buf,sizeof(print_buf),final_md5,16,opt_hexbuf));
      
      printf("raw image sha1: %s\n",
             af_hexbuf(print_buf,sizeof(print_buf),final_sha1,20,opt_hexbuf));
    }

    if(imaging_failed){
      printf("\nTHIS DRIVE COULD NOT BE IMAGED DUE TO A HARDWARE FAILURE.\n");
    }
}


Generated by  Doxygen 1.6.0   Back to index