"Fossies" - the Fresh Open Source Software Archive

Member "fatresize-1.1.0/fatresize.c" (5 Apr 2020, 18770 Bytes) of package /linux/privat/fatresize-1.1.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "fatresize.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 1.0.2_vs_1.0.3.

    1 /*
    2  * Copyright (C) 2005-2020  Anton D. Kachalov <mouse@ya.ru>
    3  *
    4  * The FAT16/FAT32 non-destructive resizer.
    5  *
    6  * This program is free software: you can redistribute it and/or modify
    7  * it under the terms of the GNU General Public License as published by
    8  * the Free Software Foundation, either version 3 of the License, or
    9  * (at your option) any later version.
   10  *
   11  * This program is distributed in the hope that it will be useful,
   12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  * GNU General Public License for more details.
   15  *
   16  * You should have received a copy of the GNU General Public License
   17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   18  */
   19 
   20 /* activate PED_ASSERT */
   21 #ifndef DEBUG
   22 #define DEBUG 1
   23 #endif
   24 
   25 #include <ctype.h>
   26 #include <errno.h>
   27 #include <getopt.h>
   28 #include <inttypes.h>
   29 #include <limits.h>
   30 #include <parted/debug.h>
   31 #include <parted/parted.h>
   32 #include <parted/unit.h>
   33 #include <stdarg.h>
   34 #include <stdio.h>
   35 #include <stdlib.h>
   36 #include <string.h>
   37 #include <unistd.h>
   38 #include <sys/stat.h>
   39 
   40 #include "config.h"
   41 
   42 #define FAT_ASSERT(cond, action) \
   43   do {                           \
   44     if (!(cond)) {               \
   45       PED_ASSERT(cond);          \
   46       action;                    \
   47     }                            \
   48   } while (0)
   49 
   50 #define MAX_SIZE_STR "max"
   51 
   52 static struct {
   53   unsigned char *fullpath;
   54   unsigned char *device;
   55   int pnum;
   56   PedSector size;
   57   int verbose;
   58   int progress;
   59   int info;
   60   int force_yes;
   61 } opts;
   62 
   63 typedef struct {
   64   time_t last_update;
   65   time_t predicted_time_left;
   66 } TimerContext;
   67 
   68 static TimerContext timer_context;
   69 
   70 static void usage(int code) {
   71   fprintf(stdout,
   72           "Usage: %s [options] device (e.g. /dev/hda1, /dev/sda2)\n"
   73           "    Resize an FAT16/FAT32 volume non-destructively:\n\n"
   74           "    -s, --size SIZE      Resize volume to SIZE[k|M|G|ki|Mi|Gi] bytes "
   75           "or \"" MAX_SIZE_STR
   76           "\"\n"
   77           "    -i, --info           Show volume information\n"
   78           "    -f, --force-yes      Do not ask questions\n"
   79           "    -n, --partition NUM  Specify partition number\n"
   80           "    -p, --progress       Show progress\n"
   81           "    -q, --quiet          Be quiet\n"
   82           "    -v, --verbose        Verbose\n"
   83           "    -h, --help           Display this help\n\n"
   84           "Please report bugs to %s\n",
   85           PACKAGE_NAME, PACKAGE_BUGREPORT);
   86 
   87   exit(code);
   88 }
   89 
   90 static void printd(int level, const char *fmt, ...) {
   91   va_list ap;
   92 
   93   if (opts.verbose < level) {
   94     return;
   95   }
   96 
   97   va_start(ap, fmt);
   98   vprintf(fmt, ap);
   99   va_end(ap);
  100 }
  101 
  102 static PedSector get_size(char *s) {
  103   PedSector size;
  104   char *suffix;
  105   int prefix_kind = 1000;
  106 
  107   if (!strncmp(s, MAX_SIZE_STR, sizeof(MAX_SIZE_STR) - 1)) return LLONG_MAX;
  108 
  109   size = strtoll(s, &suffix, 10);
  110   if (size <= 0 || errno == ERANGE) {
  111     fprintf(stderr, "Illegal new volume size\n");
  112     usage(1);
  113   }
  114 
  115   if (!*suffix) {
  116     return size;
  117   }
  118 
  119   if (strlen(suffix) == 2 && suffix[1] == 'i') {
  120     prefix_kind = 1024;
  121   } else if (strlen(suffix) > 1) {
  122     usage(1);
  123   }
  124 
  125   switch (*suffix) {
  126     case 'G':
  127       size *= prefix_kind;
  128     case 'M':
  129       size *= prefix_kind;
  130     case 'k':
  131       size *= prefix_kind;
  132       break;
  133     default:
  134       usage(1);
  135   }
  136 
  137   return size;
  138 }
  139 
  140 /* Code parts have been taken from _ped_device_probe(). */
  141 static void probe_device(PedDevice **dev, const char *path) {
  142   ped_exception_fetch_all();
  143   *dev = ped_device_get(path);
  144   if (!*dev) {
  145     ped_exception_catch();
  146   }
  147   ped_exception_leave_all();
  148 }
  149 
  150 static int get_partnum(char *dev) {
  151   int pnum;
  152   char *p;
  153 
  154   p = dev + strlen(dev) - 1;
  155   while (*p && isdigit(*p) && *p != '/') {
  156     p--;
  157   }
  158 
  159   pnum = atoi(p + 1);
  160   return pnum ? pnum : 1;
  161 }
  162 
  163 static int get_device(char *dev) {
  164   PedDevice *peddev = NULL;
  165   int len;
  166   char *devname;
  167   char *p;
  168   struct stat st;
  169 
  170   opts.device = NULL;
  171   opts.fullpath = strdup(dev);
  172 
  173   if (stat(dev, &st) == -1) {
  174     return 0;
  175   }
  176   if (!(S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode))) {
  177     probe_device(&peddev, dev);
  178     if (!peddev) {
  179       return 0;
  180     }
  181     ped_device_destroy(peddev);
  182     opts.device = strdup(dev);
  183     return 1;
  184   }
  185 
  186   len = strlen(dev);
  187   p = dev + len - 1;
  188   while (*p && isdigit(*p) && *p != '/') {
  189     p--;
  190   }
  191 
  192   devname = malloc(len);
  193   strncpy(devname, dev, p - dev + 1);
  194   devname[p - dev + 1] = '\0';
  195 
  196   if (p-dev > 2 && devname[p-dev] == 'p' && isdigit(devname[p-dev-1])) {
  197     devname[p-dev] = '\0';
  198   }
  199 
  200   peddev = NULL;
  201   probe_device(&peddev, devname);
  202   if (!peddev) {
  203     strcpy(devname, dev);
  204     probe_device(&peddev, devname);
  205 
  206     if (!peddev) {
  207       free(devname);
  208       return 0;
  209     }
  210   } else {
  211     opts.pnum = get_partnum(devname);
  212   }
  213   ped_device_destroy(peddev);
  214   opts.device = devname;
  215 
  216   return 1;
  217 }
  218 
  219 static void resize_handler(PedTimer *timer, void *ctx) {
  220   int draw_this_time;
  221   TimerContext *tctx = (TimerContext *)ctx;
  222 
  223   if (opts.verbose == -1) {
  224     return;
  225   } else if (opts.verbose < 3) {
  226     fprintf(stdout, ".");
  227     fflush(stdout);
  228     return;
  229   }
  230 
  231   if (tctx->last_update != timer->now && timer->now > timer->start) {
  232     tctx->predicted_time_left = timer->predicted_end - timer->now;
  233     tctx->last_update = timer->now;
  234     draw_this_time = 1;
  235   } else {
  236     draw_this_time = 1;
  237   }
  238 
  239   if (draw_this_time) {
  240     printf("\r                                                            \r");
  241     if (timer->state_name) {
  242       printf("%s... ", timer->state_name);
  243     }
  244     printf("%0.f%%\t(time left %.2ld:%.2ld)", 100.0 * timer->frac,
  245            tctx->predicted_time_left / 60, tctx->predicted_time_left % 60);
  246 
  247     fflush(stdout);
  248   }
  249 }
  250 
  251 static PedExceptionOption option_get_next(PedExceptionOption options,
  252                                           PedExceptionOption current) {
  253   PedExceptionOption i;
  254 
  255   if (current == 0) {
  256     i = PED_EXCEPTION_OPTION_FIRST;
  257   } else {
  258     i = current << 1;
  259   }
  260 
  261   for (; i <= options; i <<= 1) {
  262     if (options & i) {
  263       return i;
  264     }
  265   }
  266 
  267   return 0;
  268 }
  269 
  270 static PedExceptionOption ask_for_option(PedException *ex) {
  271   int i;
  272   char *buffer;
  273   size_t buffer_len;
  274   PedExceptionOption opt;
  275 
  276   for (;;) {
  277     for (i = 0, opt = option_get_next(ex->options, 0); opt;
  278          i++,   opt = option_get_next(ex->options, opt)) {
  279       printf("%s%s", i > 0 ? "/" : "\n", ped_exception_get_option_string(opt));
  280     }
  281     printf(": ");
  282     fflush(stdout);
  283 
  284     buffer = NULL;
  285     buffer_len = 0;
  286     int line_len = getline(&buffer, &buffer_len, stdin);
  287     if (line_len == -1) {
  288       free(buffer);
  289       return PED_EXCEPTION_CANCEL;
  290     }
  291     buffer[line_len-1] = '\0';
  292 
  293     for (opt = option_get_next(ex->options, 0); opt;
  294          opt = option_get_next(ex->options, opt)) {
  295       if (!strcasecmp(buffer, ped_exception_get_option_string(opt))) {
  296         free(buffer);
  297         return opt;
  298       }
  299     }
  300     free(buffer);
  301   }
  302 
  303   /* Never reach there */
  304   return PED_EXCEPTION_CANCEL;
  305 }
  306 
  307 static PedExceptionOption fatresize_handler(PedException *ex) {
  308   PedExceptionOption opt;
  309 
  310   switch (ex->type) {
  311   case PED_EXCEPTION_INFORMATION:
  312   case PED_EXCEPTION_WARNING:
  313     fprintf(opts.force_yes ? stderr : stdout,
  314             "%s: %s\n", ped_exception_get_type_string(ex->type), ex->message);
  315 
  316     if (opts.force_yes) {
  317       switch (ex->options) {
  318         case PED_EXCEPTION_IGNORE_CANCEL:
  319           return PED_EXCEPTION_IGNORE;
  320 
  321         default:
  322           /* Only one choice? Take it ;-) */
  323           opt = option_get_next(ex->options, 0);
  324           if (!option_get_next(ex->options, opt)) {
  325             return opt;
  326           }
  327           return PED_EXCEPTION_UNHANDLED;
  328       }
  329     }
  330 
  331     return ask_for_option(ex);
  332 
  333   default:
  334     if (opts.verbose != -1 || isatty(0)) {
  335       fprintf(stderr, "%s: %s\n", ped_exception_get_type_string(ex->type),
  336               ex->message);
  337     }
  338     return PED_EXCEPTION_CANCEL;
  339   }
  340 
  341 }
  342 
  343 /* This function changes "sector" to "new_sector" if the new value lies
  344  * within the required range.
  345  */
  346 static int snap(PedSector *sector, PedSector new_sector, PedGeometry *range) {
  347   FAT_ASSERT(ped_geometry_test_sector_inside(range, *sector), return 0);
  348   if (!ped_geometry_test_sector_inside(range, new_sector)) {
  349     return 0;
  350   }
  351 
  352   *sector = new_sector;
  353   return 1;
  354 }
  355 
  356 /* This function tries to replace the value in sector with a sequence
  357  * of possible replacements, given in order of preference.  The first
  358  * replacement that lies within the required range is adopted.
  359  */
  360 static void try_snap(PedSector *sector, PedGeometry *range, ...) {
  361   va_list list;
  362 
  363   va_start(list, range);
  364   while (1) {
  365     PedSector new_sector = va_arg(list, PedSector);
  366     if (new_sector == -1) {
  367       break;
  368     }
  369     if (snap(sector, new_sector, range)) {
  370       break;
  371     }
  372   }
  373   va_end(list);
  374 }
  375 
  376 /* Snaps a partition to nearby partition boundaries.  This is useful for
  377  * gobbling up small amounts of free space, and also for reinterpreting small
  378  * changes to a partition as non-changes (eg: perhaps the user only wanted to
  379  * resize the end of a partition).
  380  *  Note that this isn't the end of the story... this function is
  381  * always called before the constraint solver kicks in.  So you don't need to
  382  * worry too much about inadvertantly creating overlapping partitions, etc.
  383  */
  384 static void snap_to_boundaries(PedGeometry *new_geom, PedGeometry *old_geom,
  385                                PedDisk *disk, PedGeometry *start_range,
  386                                PedGeometry *end_range) {
  387   PedPartition *start_part;
  388   PedPartition *end_part;
  389   PedSector start = new_geom->start;
  390   PedSector end = new_geom->end;
  391 
  392   start_part = ped_disk_get_partition_by_sector(disk, start);
  393   end_part = ped_disk_get_partition_by_sector(disk, end);
  394   FAT_ASSERT(start_part, return );
  395   if (!end_part) {
  396     return;
  397   }
  398 
  399   if (old_geom) {
  400     try_snap(&start, start_range, old_geom->start, start_part->geom.start,
  401              start_part->geom.end + 1, (PedSector)-1);
  402     try_snap(&end, end_range, old_geom->end, end_part->geom.end,
  403              end_part->geom.start - 1, (PedSector)-1);
  404   } else {
  405     try_snap(&start, start_range, start_part->geom.start,
  406              start_part->geom.end + 1, (PedSector)-1);
  407     try_snap(&end, end_range, end_part->geom.end, end_part->geom.start - 1,
  408              (PedSector)-1);
  409   }
  410 
  411   FAT_ASSERT(start <= end, return );
  412   ped_geometry_set(new_geom, start, end - start + 1);
  413 }
  414 
  415 /* This functions constructs a constraint from the following information:
  416  *  start, is_start_exact, end, is_end_exact.
  417  *
  418  * If is_start_exact == 1, then the constraint requires start be as given in
  419  * "start".  Otherwise, the constraint does not set any requirements on the
  420  * start.
  421  */
  422 static PedConstraint *constraint_from_start_end(PedDevice *dev,
  423                                                 PedGeometry *range_start,
  424                                                 PedGeometry *range_end) {
  425   return ped_constraint_new(ped_alignment_any, ped_alignment_any, range_start,
  426                             range_end, 1, dev->length);
  427 }
  428 
  429 static PedConstraint *constraint_intersect_and_destroy(PedConstraint *a,
  430                                                        PedConstraint *b) {
  431   PedConstraint *result = ped_constraint_intersect(a, b);
  432   ped_constraint_destroy(a);
  433   ped_constraint_destroy(b);
  434   return result;
  435 }
  436 
  437 static int partition_warn_busy(PedPartition *part) {
  438   char *path = ped_partition_get_path(part);
  439 
  440   if (ped_partition_is_busy(part)) {
  441     ped_exception_throw(PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
  442                         ("Partition %s is being used.  You must unmount it "
  443                          "before you modify it with Parted."),
  444                         path);
  445     free(path);
  446     return 0;
  447   }
  448 
  449   free(path);
  450   return 1;
  451 }
  452 
  453 int main(int argc, char **argv) {
  454   int opt;
  455 
  456   PedDevice *dev;
  457   PedDisk *disk;
  458   PedPartition *part;
  459   PedTimer *timer = NULL;
  460 
  461   char *old_str;
  462   char *def_str;
  463   PedFileSystem *fs;
  464   PedSector start, end;
  465   PedGeometry part_geom;
  466   PedGeometry new_geom;
  467   PedGeometry *old_geom;
  468   PedConstraint *constraint;
  469   PedGeometry *range_start, *range_end;
  470 
  471   static const char *sopt = "-hfin:s:vpq";
  472   static const struct option lopt[] = {{"help", no_argument, NULL, 'h'},
  473                                        {"force-yes", no_argument, NULL, 'f'},
  474                                        {"info", no_argument, NULL, 'i'},
  475                                        {"partition", required_argument, NULL, 'n'},
  476                                        {"progress", no_argument, NULL, 'p'},
  477                                        {"size", required_argument, NULL, 's'},
  478                                        {"verbose", no_argument, NULL, 'v'},
  479                                        {"quiet", no_argument, NULL, 'q'},
  480                                        {NULL, 0, NULL, 0}};
  481 
  482   memset(&opts, 0, sizeof(opts));
  483 
  484   if (argc < 2) {
  485     usage(0);
  486   }
  487 
  488   opts.pnum = -1;
  489 
  490   while ((opt = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
  491     switch (opt) {
  492       case 1:
  493         if (!opts.device) {
  494           get_device(optarg);
  495         } else {
  496           usage(1);
  497         }
  498         break;
  499 
  500       case 'n':
  501         opts.pnum = atoi(optarg);
  502         break;
  503 
  504       case 'f':
  505         opts.force_yes = 1;
  506         break;
  507 
  508       case 'i':
  509         opts.info = 1;
  510         break;
  511 
  512       case 'p':
  513         opts.progress = 1;
  514         break;
  515 
  516       case 's':
  517         opts.size = get_size(optarg);
  518         break;
  519 
  520       case 'v':
  521         opts.verbose++;
  522         break;
  523 
  524       case 'q':
  525         opts.verbose = -1;
  526         break;
  527 
  528       case 'h':
  529       case '?':
  530       default:
  531         printd(0, "%s (%s)\n", PACKAGE_STRING, BUILD_DATE);
  532         usage(0);
  533     }
  534   }
  535 
  536   printd(0, "%s (%s)\n", PACKAGE_STRING, BUILD_DATE);
  537 
  538   if (!opts.device) {
  539     fprintf(stderr, "You must specify exactly one existing device.\n");
  540     return 1;
  541   } else if (!opts.size && !opts.info) {
  542     fprintf(stderr, "You must specify new size.\n");
  543     return 1;
  544   }
  545 
  546   ped_exception_set_handler(fatresize_handler);
  547 
  548   if (opts.progress) {
  549     timer = ped_timer_new(resize_handler, &timer_context);
  550     timer_context.last_update = 0;
  551   }
  552 
  553   printd(3, "ped_device_get(%s)\n", opts.device);
  554   dev = ped_device_get(opts.device);
  555   if (!dev) {
  556     return 1;
  557   }
  558 
  559   printd(3, "ped_device_open()\n");
  560   if (!ped_device_open(dev)) {
  561     return 1;
  562   }
  563 
  564   if (opts.pnum > 0) {
  565     printd(3, "ped_disk_new()\n");
  566     disk = ped_disk_new(dev);
  567     if (!disk) {
  568       return 1;
  569     }
  570 
  571     printd(3, "ped_disk_get_partition(%d)\n", opts.pnum);
  572     part = ped_disk_get_partition(disk, opts.pnum);
  573     if (!part || !part->fs_type) {
  574       return 1;
  575     }
  576 
  577     if (strncmp(part->fs_type->name, "fat", 3)) {
  578       ped_exception_throw(PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
  579                           "%s is not valid FAT16/FAT32 partition.",
  580                           opts.fullpath);
  581       return 1;
  582     }
  583 
  584     if (!partition_warn_busy(part)) {
  585       ped_disk_destroy(disk);
  586       return 1;
  587     }
  588     memcpy(&part_geom, &part->geom, sizeof(PedGeometry));
  589   } else {
  590     if (!ped_geometry_init(&part_geom, dev, 0, dev->length)) {
  591       return 1;
  592     }
  593   }
  594 
  595   printf("part(start=%llu, end=%llu, length=%llu)\n",
  596     part_geom.start, part_geom.end, part_geom.length);
  597 
  598   if (opts.info || opts.size == LLONG_MAX) {
  599     printd(3, "ped_file_system_open()\n");
  600     fs = ped_file_system_open(&part_geom);
  601     if (!fs) {
  602       return 1;
  603     }
  604 
  605     printd(3, "ped_file_system_get_resize_constraint()\n");
  606     constraint = ped_file_system_get_resize_constraint(fs);
  607     if (!constraint) {
  608       return 1;
  609     }
  610   }
  611   if (opts.info) {
  612     printf("FAT: %s\n", fs->type->name);
  613     printf("Cur size: %llu\n", fs->geom->length * dev->sector_size);
  614     printf("Min size: %llu\n", constraint->min_size * dev->sector_size);
  615     printf("Max size: %llu\n", constraint->max_size * dev->sector_size);
  616 
  617     ped_constraint_destroy(constraint);
  618     return 0;
  619   }
  620   if (opts.size == LLONG_MAX) {
  621     opts.size = constraint->max_size * dev->sector_size;
  622     ped_constraint_destroy(constraint);
  623   }
  624 
  625   start = part_geom.start;
  626   printd(3, "ped_geometry_new(%llu)\n", start);
  627   range_start = ped_geometry_new(dev, start, 1);
  628   if (!range_start) {
  629     return 1;
  630   }
  631 
  632   end = part_geom.start + opts.size / dev->sector_size;
  633   printd(3, "ped_unit_parse(%llu)\n", end);
  634   old_str = ped_unit_format(dev, part_geom.end);
  635   def_str = ped_unit_format(dev, end);
  636   if (!strcmp(old_str, def_str)) {
  637     range_end = ped_geometry_new(dev, part_geom.end, 1);
  638     if (!range_end) {
  639       return 1;
  640     }
  641   } else if (!ped_unit_parse(def_str, dev, &end, &range_end)) {
  642     return 1;
  643   }
  644   free(old_str);
  645   free(def_str);
  646 
  647   printd(3, "ped_geometry_duplicate()\n");
  648   old_geom = ped_geometry_duplicate(&part_geom);
  649   if (!old_geom) {
  650     return 1;
  651   }
  652 
  653   printd(3, "ped_geometry_init(%llu, %llu)\n", start, end - start + 1);
  654   if (!ped_geometry_init(&new_geom, dev, start, end - start + 1)) {
  655     return 1;
  656   }
  657 
  658   printd(3, "snap_to_boundaries()\n");
  659   snap_to_boundaries(&new_geom, &part_geom, disk, range_start, range_end);
  660 
  661   printd(3, "ped_file_system_open()\n");
  662   fs = ped_file_system_open(&part->geom);
  663   if (!fs) {
  664     return 1;
  665   }
  666 
  667   printd(3, "constraint_intersect_and_destroy()\n");
  668   constraint = constraint_intersect_and_destroy(
  669       ped_file_system_get_resize_constraint(fs),
  670       constraint_from_start_end(dev, range_start, range_end));
  671   if (!constraint) {
  672     return 1;
  673   }
  674 
  675   /* Resize partition */
  676   if (opts.pnum > 0) {
  677     printd(3, "ped_disk_set_partition_geom(%llu, %llu)\n", new_geom.start,
  678          new_geom.end);
  679     if (!ped_disk_set_partition_geom(disk, part, constraint, new_geom.start,
  680                                      new_geom.end)) {
  681       ped_file_system_close(fs);
  682       ped_constraint_destroy(constraint);
  683       return 1;
  684     }
  685   }
  686 
  687   printd(1, "Resizing file system.\n");
  688   if (!ped_file_system_resize(fs, &new_geom, timer)) {
  689     return 1;
  690   }
  691   if (opts.verbose == 3 && opts.progress) {
  692     printf("\n");
  693   }
  694 
  695   printd(1, "Done.\n");
  696   /* May have changed... fat16 -> fat32 */
  697   if (opts.pnum > 0) {
  698     ped_partition_set_system(part, fs->type);
  699   }
  700   ped_file_system_close(fs);
  701   ped_constraint_destroy(constraint);
  702 
  703   if (opts.pnum > 0) {
  704     printd(1, "Committing changes.\n");
  705     if (!ped_disk_commit(disk)) {
  706       return 1;
  707     }
  708     ped_disk_destroy(disk);
  709   }
  710 
  711   if (dev->boot_dirty && dev->type != PED_DEVICE_FILE) {
  712     ped_exception_throw(PED_EXCEPTION_WARNING, PED_EXCEPTION_OK,
  713                         ("You should reinstall your boot loader."
  714                          "Read section 4 of the Parted User "
  715                          "documentation for more information."));
  716   }
  717 
  718   ped_device_close(dev);
  719 
  720   return 0;
  721 }