/* $NetBSD$ */ /* * File "udfclient.c" is part of the UDFclient toolkit. * File $Id: udfclient.c,v 1.90 2007/12/10 14:41:08 reinoud Exp $ $Name: $ * * Copyright (c) 2003, 2004, 2005, 2006 Reinoud Zandijk * All rights reserved. * * The UDFclient toolkit is distributed under the Clarified Artistic Licence. * A copy of the licence is included in the distribution as * `LICENCE.clearified.artistic' and a copy of the licence can also be * requested at the GNU foundantion's website. * * Visit the UDFclient toolkit homepage http://www.13thmonkey.org/udftoolkit/ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include "udf.h" #include "udf_bswap.h" /* switches */ /* #define DEBUG(a) (a) */ #define DEBUG(a) if (0) { a; } #ifndef MAX #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)<(b)?(a):(b)) #endif /* include timeval to timespec conversion macro's for systems that don't provide them */ #ifndef TIMEVAL_TO_TIMESPEC # define TIMEVAL_TO_TIMESPEC(tv, ts) do { \ (ts)->tv_sec = (tv)->tv_sec; \ (ts)->tv_nsec = (tv)->tv_usec * 1000; \ } while (/*CONSTCOND*/0) #endif #ifndef TIMESPEC_TO_TIMEVAL # define TIMESPEC_TO_TIMEVAL(tv, ts) do { \ (tv)->tv_sec = (ts)->tv_sec; \ (tv)->tv_usec = (ts)->tv_nsec / 1000; \ } while (/*CONSTCOND*/0) #endif /* include the dump parts ... in order to get a more sane splitting */ extern void udf_dump_alive_sets(void); /* globals */ extern int udf_verbose; extern int uscsilib_verbose; int read_only; #define MAX_ARGS 100 struct curdir { char *name; struct udf_mountpoint *mountpoint; /* foreign */ struct udf_node *udf_node; /* foreign */ struct hash_entry *udf_mountpoint_entry; /* `current' mountpoint entry */ } curdir; /* * XXX * FTP client; de volumes vooraan zetten in de VFS ; evt. in meerdere subdirs. * general file name format * * /volset:pri:log/udfpath * of * /volset/pri/log/udfpath/ * * XXX */ int udfclient_readdir(struct udf_node *udf_node, struct uio *result_uio, int *eof_res) { struct dirent entry; struct udf_mountpoint *mountable; assert(result_uio); if (!udf_node) { /* mountables */ /* XXX result_uio needs to be long enough to hold all mountables!!!! XXX */ SLIST_FOREACH(mountable, &udf_mountables, all_next) { strcpy(entry.d_name, mountable->mount_name); entry.d_type = DT_DIR; uiomove(&entry, sizeof(struct dirent), result_uio); } if (eof_res) *eof_res = 1; return 0; } /* intree nodes : pass on to udf_readdir */ return udf_readdir(udf_node, result_uio, eof_res); } /* VOP_LOOKUP a-like */ int udfclient_lookup(struct udf_node *dir_node, struct udf_node **vnode, char *name) { struct udf_mountpoint *mountable; assert(vnode); assert(name); *vnode = NULL; if (!dir_node) { /* mountables */ SLIST_FOREACH(mountable, &udf_mountables, all_next) { if (strcmp(mountable->mount_name, name) == 0) { /* found `root' of a mountable */ *vnode = mountable->rootdir_node; return 0; } } return ENOENT; } /* intree nodes : pass on to udf_lookup */ return udf_lookup(dir_node, vnode, name); } int udfclient_getattr(struct udf_node *udf_node, struct stat *stat) { int error; error = 0; if (udf_node) { error = udf_getattr(udf_node, stat); /* print? */ if (error) fprintf(stderr, "Can't stat file\n"); } else { /* dummy entry for `root' in VFS */ stat->st_mode = 0744 | S_IFDIR; stat->st_size = 0; stat->st_uid = 0; stat->st_gid = 0; } return error; } /* higher level of lookup; walk tree */ /* results in a `held'/locked node upto `root' */ int udfclient_lookup_pathname(struct udf_node *cur_node, struct udf_node **res_node, char *restpath_given) { struct udf_node *sub_node; char *restpath, *next_element, *slashpos, *pathpos; int error; assert(restpath_given); restpath = strdup(restpath_given); /* start at root */ *res_node = NULL; pathpos = restpath; assert(*pathpos == '/'); pathpos++; /* strip leading '/' */ next_element = pathpos; while (next_element && (strlen(next_element) > 0)) { /* determine next slash position */ slashpos = strchr(next_element, '/'); if (slashpos) *slashpos++ = 0; error = udfclient_lookup(cur_node, &sub_node, next_element); if (error) { free(restpath); return error; } /* advance */ cur_node = sub_node; next_element = slashpos; } /* we are at the end; return result */ *res_node = cur_node; free(restpath); return 0; } char *udfclient_realpath(char *cur_path, char *relpath, char **leaf) { char *resultpath, *here, *pos; resultpath = calloc(1, sizeof(cur_path) + sizeof(relpath) +1024); assert(resultpath); strcpy(resultpath, "/"); strcat(resultpath, cur_path); strcat(resultpath, "/"); /* check if we are going back to `root' */ if (relpath && *relpath == '/') { strcpy(resultpath, ""); } strcat(resultpath, relpath); /* now clean up the resulting string by removing double slashes */ here = resultpath; while (*here) { pos = here; while (strncmp(pos, "//", 2) == 0) pos++; if (pos != here) strcpy(here, pos); here++; } /* remove '.' and '..' sequences */ here = resultpath; while (*here) { /* printf("transformed to %s\n", resultpath); */ /* check for internal /./ and trailing /. */ if (strncmp(here, "/./", 3) == 0) { strcpy(here+1, here + 3); continue; } if (strcmp(here, "/.")==0) { strcpy(here+1, here + 2); continue; } if (strncmp(here, "/../", 4) == 0) { strcpy(here+1, here + 4); /* go for the parent */ pos = here-1; while (*pos && *pos != '/') pos--; pos++; strcpy(pos, here+1); here = pos; continue; } if (strcmp(here, "/..")==0) { strcpy(here+1, here + 3); /* go for the parent */ pos = here-1; while (*pos && *pos != '/') pos--; pos++; strcpy(pos, here+1); here = pos; continue; } here++; } if (leaf) { /* find the leaf name */ here = resultpath; while (*here) { if (strncmp(here, "/", 1) == 0) *leaf = here+1; here++; } } return resultpath; } static void print_dir_entry(struct udf_node *udf_node, char *name) { struct stat stat; uint64_t size; int mode, this_mode, uid, gid; int error; error = udfclient_getattr(udf_node, &stat); if (error) return; size = stat.st_size; mode = stat.st_mode; uid = stat.st_uid; gid = stat.st_gid; if (mode & S_IFDIR) printf("d"); else printf("-"); mode = mode & 511; this_mode = (mode >> 6) & 7; printf("%c%c%c", "----rrrr"[this_mode & 4], "--www"[this_mode & 2], "-x"[this_mode & 1]); this_mode = (mode >> 3) & 7; printf("%c%c%c", "----rrrr"[this_mode & 4], "--www"[this_mode & 2], "-x"[this_mode & 1]); this_mode = mode & 7; printf("%c%c%c", "----rrrr"[this_mode & 4], "--www"[this_mode & 2], "-x"[this_mode & 1]); printf(" %5d %5d %10lu %s\n", gid, uid, (unsigned long) size, name); } #define LS_SUBTREE_DIR_BUFFER_SIZE (16*1024) void udfclient_ls(int args, char *arg1) { struct udf_node *udf_node, *entry_node; uint8_t *buffer; struct uio dir_uio; struct iovec dir_uiovec; struct dirent *dirent; struct stat stat; uint32_t pos; int eof; char *node_name, *leaf_name; int error; if (args > 1) { printf("Syntax: ls [file | dir]\n"); return; } if (args == 0) arg1 = ""; node_name = udfclient_realpath(curdir.name, arg1, &leaf_name); error = udfclient_lookup_pathname(NULL, &udf_node, node_name); if (error) { fprintf(stderr, "%s : %s\n", arg1, strerror(error)); free(node_name); return; } error = udfclient_getattr(udf_node, &stat); if (stat.st_mode & S_IFDIR) { printf("Directory listing of %s\n", udf_node ? leaf_name : "/"); /* start at the start of the directory */ dir_uio.uio_offset = 0; dir_uio.uio_iov = &dir_uiovec; dir_uio.uio_iovcnt = 1; buffer = calloc(1, LS_SUBTREE_DIR_BUFFER_SIZE); if (!buffer) return; do { dir_uiovec.iov_base = buffer; dir_uiovec.iov_len = LS_SUBTREE_DIR_BUFFER_SIZE; dir_uio.uio_resid = LS_SUBTREE_DIR_BUFFER_SIZE; dir_uio.uio_rw = UIO_WRITE; error = udfclient_readdir(udf_node, &dir_uio, &eof); if (error) { fprintf(stderr, "error during readdir: %s\n", strerror(error)); break; } pos = 0; while (pos < (LS_SUBTREE_DIR_BUFFER_SIZE - dir_uio.uio_resid)) { dirent = (struct dirent *) (buffer + pos); error = udfclient_lookup(udf_node, &entry_node, dirent->d_name); print_dir_entry(entry_node, dirent->d_name); pos += sizeof(struct dirent); } } while (!eof); free(buffer); } else { print_dir_entry(udf_node, leaf_name); } free(node_name); } #undef LS_SUBTREE_DIR_BUFFER_SIZE void udfclient_pwd(int args) { char pwd[1024]; if (args) { printf("Syntax: pwd\n"); return; } getcwd(pwd, 1024); printf("UDF working directory is %s\n", curdir.name); printf("Current FS working directory %s\n", pwd); } static void udfclient_print_free_amount(char *prefix, uint64_t value, uint64_t max_value) { printf("%s %10"PRIu64" Kb (%3"PRIu64" %%) (%8.2f Mb) (%5.2f Gb)\n", prefix, value/1024, (100*value)/max_value, (double) value/(1024*1024), (double) value/(1024*1024*1024)); } void udfclient_free(int args) { struct udf_part_mapping *part_mapping; struct udf_partition *udf_partition; struct udf_log_vol *udf_log_vol; struct logvol_desc *lvd; uint64_t part_size, unalloc_space, freed_space; uint64_t total_space, free_space, await_alloc_space; uint32_t sector_size, lb_size; int part_num; if (args) { printf("Syntax: free\n"); return; } if (!curdir.udf_node || !(udf_log_vol = curdir.udf_node->udf_log_vol)) { printf("Can only report free space in UDF mountpoints\n"); return; } lb_size = udf_log_vol->lb_size; sector_size = udf_log_vol->sector_size; lvd = udf_log_vol->log_vol; udf_dump_id("Logical volume ", 128, lvd->logvol_id, &lvd->desc_charset); total_space = udf_log_vol->total_space; free_space = udf_log_vol->free_space; await_alloc_space = udf_log_vol->await_alloc_space; SLIST_FOREACH(part_mapping, &udf_log_vol->part_mappings, next_mapping) { part_num = part_mapping->udf_virt_part_num; udf_logvol_vpart_to_partition(udf_log_vol, part_num, NULL, &udf_partition); assert(udf_partition); unalloc_space = udf_partition->free_unalloc_space; freed_space = udf_partition->free_freed_space; part_size = udf_partition->part_length; switch (part_mapping->udf_part_mapping_type) { case UDF_PART_MAPPING_PHYSICAL : printf("\tphysical partition %d\n", part_num); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) size\n", part_size/1024, part_size / lb_size); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) unallocated\n", unalloc_space/1024, unalloc_space / lb_size); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) freed\n", freed_space/1024, freed_space / lb_size); break; case UDF_PART_MAPPING_VIRTUAL : printf("\tvirtual partition mapping %d\n", part_num); printf("\t\tnot applicable\n"); break; case UDF_PART_MAPPING_SPARABLE : printf("\tsparable partition %d\n", part_num); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) size\n", part_size/1024, part_size / lb_size); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) unallocated\n", unalloc_space/1024, unalloc_space / lb_size); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) freed\n", freed_space/1024, freed_space / lb_size); break; case UDF_PART_MAPPING_META : printf("\tmetadata 'partition' %d\n", part_num); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) unallocated\n", unalloc_space/1024, unalloc_space / lb_size); printf("\t\t%8"PRIu64" K (%"PRIu64" pages) freed\n", freed_space/1024, freed_space / lb_size); break; case UDF_PART_MAPPING_ERROR : printf("\terror partiton %d\n", part_num); break; default: break; } } printf("\n"); udfclient_print_free_amount("\tConfirmed free space ", free_space, total_space); udfclient_print_free_amount("\tAwaiting allocation ", await_alloc_space, total_space); udfclient_print_free_amount("\tEstimated free space ", free_space - await_alloc_space, total_space); udfclient_print_free_amount("\tEstimated total used ", total_space - free_space + await_alloc_space, total_space); printf("\n"); udfclient_print_free_amount("\tTotal size ", total_space, total_space); } void udfclient_cd(int args, char *arg1) { struct udf_node *udf_node; struct stat stat; char *node_name, *new_curdir_name; int error; if (args > 1) { printf("Syntax: cd [dir]\n"); return; } new_curdir_name = udfclient_realpath(curdir.name, arg1, NULL); node_name = strdup(new_curdir_name); /* working copy */ error = udfclient_lookup_pathname(NULL, &udf_node, node_name); if (error) { fprintf(stderr, "%s : %s\n", arg1, strerror(error)); free(new_curdir_name); free(node_name); return; } error = udfclient_getattr(udf_node, &stat); if (stat.st_mode & S_IFDIR) { free(curdir.name); curdir.name = new_curdir_name; curdir.udf_node = udf_node; free(node_name); udfclient_pwd(0); } else { fprintf(stderr, "%s is not a directory\n", arg1); free(new_curdir_name); free(node_name); } } void udfclient_lcd(int args, char *arg1) { char pwd[1024]; if (args > 1) { printf("Syntax: lcd [dir]\n"); return; } if (strcmp(arg1, "" )==0) arg1 = getenv("HOME"); if (strcmp(arg1, "~")==0) arg1 = getenv("HOME"); if (chdir(arg1)) { fprintf(stderr, "While trying to go to %s :", arg1); perror(""); } getcwd(pwd, 1024); printf("Changing local directory to %s\n", pwd); } void udfclient_lls(int args) { if (args) { printf("Syntax: lls\n"); return; } if (system("/bin/ls")) { perror("While listing current directory\n"); } } uint64_t getmtime(void) { struct timeval tp; gettimeofday(&tp, NULL); return 1000000*tp.tv_sec + tp.tv_usec; } int udfclient_get_file(struct udf_node *udf_node, char *fullsrcname, char *fulldstname) { struct uio file_uio; struct iovec file_iov; struct stat stat; struct timeval times[2]; uint64_t file_length; uint64_t start, now, then, eta; uint64_t cur_speed, avg_speed, data_transfered; uint64_t file_block_size, file_transfer_size; uint8_t *file_block; char cur_txt[32], avg_txt[32], eta_txt[32]; int fileh, len; int notok, error; assert(udf_node); assert(fullsrcname); assert(strlen(fullsrcname) >= 1); error = 0; /* terminal directory node? */ error = udfclient_getattr(udf_node, &stat); if (stat.st_mode & S_IFDIR) { len = strlen(fulldstname); if (strcmp(fulldstname + len -2, "/.") == 0) fulldstname[len-2] = 0; if (strcmp(fulldstname + len -3, "/..") == 0) return 0; error = mkdir(fulldstname, (udf_node->stat.st_mode) & 07777); if (!error) { /* set owner attribute and times; access permissions allready set on creation.*/ notok = chown(fulldstname, stat.st_uid, stat.st_gid); if (notok) fprintf(stderr, "failed to set owner of directory, ignoring\n"); TIMESPEC_TO_TIMEVAL(×[0], &stat.st_atimespec); /* access time */ TIMESPEC_TO_TIMEVAL(×[1], &stat.st_mtimespec); /* modification time */ notok = utimes(fulldstname, times); if (notok) fprintf(stderr, "failed to set times on directory, ignoring\n"); } if (error) fprintf(stderr, "While creating directory `%s' : %s\n", fulldstname, strerror(errno)); return error; } /* terminal file node; setting mode correctly */ fileh = open(fulldstname, O_WRONLY | O_CREAT, udf_node->stat.st_mode); if (fileh >= 0) { file_length = udf_node->stat.st_size; file_block_size = 256*1024; /* block read in length */ file_block = malloc(file_block_size); if (!file_block) { printf("Out of memory claiming file buffer\n"); return ENOMEM; } /* move to uio_newuio(struct uio *uio) with fixed length uio_iovcnt? */ bzero(&file_uio, sizeof(struct uio)); file_uio.uio_rw = UIO_WRITE; /* WRITE into this space */ file_uio.uio_iovcnt = 1; file_uio.uio_iov = &file_iov; start = getmtime(); then = now = start; eta = data_transfered = 0; strcpy(avg_txt, "---"); strcpy(cur_txt, "---"); strcpy(eta_txt, "---"); file_uio.uio_offset = 0; /* begin at the start */ do { /* fill in IO vector space; reuse blob file_block over and over */ file_transfer_size = MIN(file_block_size, file_length - file_uio.uio_offset); file_uio.uio_resid = file_transfer_size; file_uio.uio_iov->iov_base = file_block; file_uio.uio_iov->iov_len = file_block_size; error = udf_read_file_part_uio(udf_node, fullsrcname, UDF_C_USERDATA, &file_uio); if (error) { fprintf(stderr, "While retrieving file block : %s\n", strerror(error)); printf("\n\n\n"); /* XXX */ break; } write(fileh, file_block, file_transfer_size); if ((getmtime() - now > 1000000) || (file_uio.uio_offset >= file_length)) { if (strlen(fulldstname) < 45) { printf("\r%-45s ", fulldstname); } else { printf("\r...%-42s ", fulldstname + strlen(fulldstname)-42); } printf("%10"PRIu64" / %10"PRIu64" bytes ", (uint64_t) file_uio.uio_offset, (uint64_t) file_length); if (file_length) printf("(%3d%%) ", (int) (100.0*(float) file_uio.uio_offset / file_length)); then = now; now = getmtime(); cur_speed = 0; avg_speed = 0; if (now-start > 0) avg_speed = (1000000 * file_uio.uio_offset) / (now-start); if (now-then > 0) cur_speed = (1000000 * (file_uio.uio_offset - data_transfered)) / (now-then); if (avg_speed > 0) eta = (file_length - file_uio.uio_offset) / avg_speed; data_transfered = file_uio.uio_offset; strcpy(avg_txt, "---"); strcpy(cur_txt, "---"); strcpy(eta_txt, "---"); if (avg_speed > 0) sprintf(avg_txt, "%d", (int32_t) avg_speed/1000); if (cur_speed > 0) sprintf(cur_txt, "%d", (int32_t) cur_speed/1000); if (eta > 0) sprintf(eta_txt, "%02d:%02d:%02d", (int) (eta/3600), (int) (eta/60) % 60, (int) eta % 60); printf("%6s KB/s (%6s KB/s) ETA %s", avg_txt, cur_txt, eta_txt); fflush(stdout); } } while ((file_uio.uio_offset < file_length)); printf(" finished\n"); free(file_block); /* set owner attribute and times; access permissions allready set on creation.*/ notok = fchown(fileh, stat.st_uid, stat.st_gid); if (notok) fprintf(stderr, "failed to set owner of file, ignoring\n"); TIMESPEC_TO_TIMEVAL(×[0], &stat.st_atimespec); /* access time */ TIMESPEC_TO_TIMEVAL(×[1], &stat.st_mtimespec); /* modification time */ notok = futimes(fileh, times); if (notok) fprintf(stderr, "failed to set times on directory, ignoring\n"); close(fileh); } else { printf("Help! can't open file %s for output\n", fulldstname); } return error; } #define GET_SUBTREE_DIR_BUFFER_SIZE (16*1024) void udfclient_get_subtree(struct udf_node *udf_node, char *srcprefix, char *dstprefix, int recurse, uint64_t *total_size) { struct uio dir_uio; struct iovec dir_iovec; uint8_t *buffer; uint32_t pos; char fullsrcpath[1024], fulldstpath[1024]; /* XXX arbitrary length XXX */ struct dirent *dirent; struct stat stat; struct udf_node *entry_node; int new_recurse, eof; int error; if (!udf_node) return; udf_node->hold++; error = udfclient_getattr(udf_node, &stat); if ((stat.st_mode & S_IFDIR) && recurse) { buffer = malloc(GET_SUBTREE_DIR_BUFFER_SIZE); if (!buffer) { udf_node->hold--; return; } /* recurse into this directory */ dir_uio.uio_offset = 0; /* begin at start */ do { dir_iovec.iov_base = buffer; dir_iovec.iov_len = GET_SUBTREE_DIR_BUFFER_SIZE; dir_uio.uio_resid = GET_SUBTREE_DIR_BUFFER_SIZE; dir_uio.uio_iovcnt = 1; dir_uio.uio_iov = &dir_iovec; dir_uio.uio_rw = UIO_WRITE; error = udf_readdir(udf_node, &dir_uio, &eof); pos = 0; while (pos < GET_SUBTREE_DIR_BUFFER_SIZE - dir_uio.uio_resid) { dirent = (struct dirent *) (buffer + pos); sprintf(fullsrcpath, "%s/%s", srcprefix, dirent->d_name); sprintf(fulldstpath, "%s/%s", dstprefix, dirent->d_name); error = udf_lookup(udf_node, &entry_node, dirent->d_name); new_recurse = (strcmp(dirent->d_name, ".") && strcmp(dirent->d_name, "..")); udfclient_get_subtree(entry_node, fullsrcpath, fulldstpath, new_recurse, total_size); pos += sizeof(struct dirent); /* XXX variable size dirents possible XXX */ } } while (!eof); udf_node->hold--; free(buffer); return; } /* leaf node : prefix is complete name but with `/' prefix */ if (*srcprefix == '/') srcprefix++; error = udfclient_get_file(udf_node, srcprefix, dstprefix); udf_node->hold--; if (!error) *total_size += udf_node->stat.st_size; } #undef GET_SUBTREE_DIR_BUFFER_SIZE void udfclient_get(int args, char *arg1, char *arg2) { struct udf_node *udf_node; char *source_name, *target_name; uint64_t start, now, totalsize, avg_speed; int error; if (args > 2) { printf("Syntax: get remote [local]\n"); return; } source_name = arg1; target_name = arg1; if (args == 2) target_name = arg2; /* source name gets substituted */ source_name = udfclient_realpath(curdir.name, source_name, NULL); DEBUG(printf("Attempting to retrieve %s\n", source_name)); error = udfclient_lookup_pathname(NULL, &udf_node, source_name); if (error) { fprintf(stderr, "%s : %s\n", arg1, strerror(error)); free(source_name); return; } /* get the file/dir tree */ totalsize = 0; start = getmtime(); udfclient_get_subtree(udf_node, source_name, target_name, 1, &totalsize); now = getmtime(); if (now-start > 0) { avg_speed = (1000000 * totalsize) / (now-start); printf("A total of %d kb transfered at an overal average of %d kb/sec\n", (uint32_t)totalsize/1024, (uint32_t)avg_speed/1024); } else { printf("Transfered %d kb\n", (uint32_t) totalsize/1024); } /* bugalert: not releasing target_name for its not substituted */ free(source_name); } void udfclient_mget(int args, char *argv[]) { struct udf_node *udf_node; uint64_t start, now, totalsize, avg_speed; char *node_name, *source_name, *target_name; int arg, error; if (args == 0) { printf("Syntax: mget (file | dir)*\n"); return; } /* retrieve the series of file/dir trees and measure time/seed */ totalsize = 0; start = getmtime(); /* process all args */ arg = 0; node_name = NULL; while (args) { source_name = target_name = argv[arg]; node_name = udfclient_realpath(curdir.name, source_name, NULL); DEBUG(printf("Attempting to retrieve %s\n", node_name)); error = udfclient_lookup_pathname(NULL, &udf_node, node_name); printf("%d: mget trying %s\n", error, node_name); if (!error) { udfclient_get_subtree(udf_node, source_name, target_name, 1, &totalsize); } if (node_name) { free(node_name); node_name = NULL; } if (error) break; /* TODO continuation flag? */ /* advance */ arg++; args--; } now = getmtime(); if (now-start > 0) { avg_speed = (1000000 * totalsize) / (now-start); printf("A total of %d kb transfered at an overal average of %d kb/sec\n", (uint32_t)totalsize/1024, (uint32_t)avg_speed/1024); } else { printf("Transfered %d kb\n", (uint32_t) totalsize/1024); } if (node_name) free(node_name); } int udfclient_put_file(struct udf_node *udf_node, char *fullsrcname, char *fulldstname) { struct uio file_uio; struct iovec file_iov; uint64_t file_length; uint64_t start, now, then, eta; uint64_t cur_speed, avg_speed, data_transfered; uint64_t file_block_size, file_transfer_size; uint8_t *file_block; char cur_txt[32], avg_txt[32], eta_txt[32]; int fileh; int error, printed; assert(udf_node); assert(fullsrcname); DEBUG(printf("Attempting to write %s\n", fullsrcname)); fileh = open(fullsrcname, O_RDONLY, 0666); if (fileh == -1) { fprintf(stderr, "Can't open local file %s for reading: %s\n", fullsrcname, strerror(errno)); return ENOENT; } /* get file length */ file_length = lseek(fileh, 0, SEEK_END); lseek(fileh, 0, SEEK_SET); /* check if file will fit; give it a bit of slack space until the space issue is found and fixed */ if (udf_node->udf_log_vol->free_space < file_length + udf_node->udf_log_vol->await_alloc_space + UDF_MINFREE_LOGVOL) { return ENOSPC; } /* allocate file block to transfer file with */ file_block_size = 128*1024; file_block = malloc(file_block_size); if (!file_block) { fprintf(stderr, "Out of memory claiming file buffer\n"); return ENOMEM; } /* move to uio_newuio(struct uio *uio) with fixed length uio_iovcnt? */ bzero(&file_uio, sizeof(struct uio)); file_uio.uio_rw = UIO_READ; /* READ from this space */ file_uio.uio_iovcnt = 1; file_uio.uio_iov = &file_iov; /* ------------ */ start = getmtime(); then = now = start; eta = data_transfered = 0; printed = 0; strcpy(avg_txt, "---"); strcpy(cur_txt, "---"); strcpy(eta_txt, "---"); /* ------------ */ error = 0; error = udf_truncate_node(udf_node, 0); while (!error && (file_uio.uio_offset < file_length)) { file_transfer_size = MIN(file_block_size, file_length - file_uio.uio_offset); error = read(fileh, file_block, file_transfer_size); if (error<0) { fprintf(stderr, "While reading in file block for writing : %s\n", strerror(errno)); error = errno; break; } file_uio.uio_resid = file_transfer_size; file_uio.uio_iov->iov_base = file_block; file_uio.uio_iov->iov_len = file_block_size; error = udf_write_file_part_uio(udf_node, fullsrcname, UDF_C_USERDATA, &file_uio); if (error) { fprintf(stderr, "\nError while writing file : %s\n", strerror(error)); break; } /* ------------ */ if ((getmtime() - now > 1000000) || (file_uio.uio_offset >= file_length)) { printed = 1; if (strlen(fulldstname) < 45) { printf("\r%-45s ", fulldstname); } else { printf("\r...%-42s ", fulldstname+strlen(fulldstname)-42); } printf("%10"PRIu64" / %10"PRIu64" bytes ", (uint64_t) file_uio.uio_offset, (uint64_t) file_length); if (file_length) printf("(%3d%%) ", (int) (100.0*(float) file_uio.uio_offset / file_length)); then = now; now = getmtime(); cur_speed = 0; avg_speed = 0; if (now-start > 0) avg_speed = (1000000 * file_uio.uio_offset) / (now-start); if (now-then > 0) cur_speed = (1000000 * (file_uio.uio_offset - data_transfered)) / (now-then); if (avg_speed > 0) eta = (file_length - file_uio.uio_offset) / avg_speed; data_transfered = file_uio.uio_offset; strcpy(avg_txt, "---"); strcpy(cur_txt, "---"); strcpy(eta_txt, "---"); if (avg_speed > 0) sprintf(avg_txt, "%d", (int32_t) avg_speed/1024); if (cur_speed > 0) sprintf(cur_txt, "%d", (int32_t) cur_speed/1024); if (eta > 0) sprintf(eta_txt, "%02d:%02d:%02d", (int) (eta/3600), (int) (eta/60) % 60, (int) eta % 60); printf("%6s KB/s (%6s KB/s) ETA %s", avg_txt, cur_txt, eta_txt); fflush(stdout); } /* ------------ */ } if (!error && printed) printf(" finished\n"); close(fileh); free(file_block); return error; } int udfclient_put_subtree(struct udf_node *parent_node, char *srcprefix, char *srcname, char *dstprefix, char *dstname, uint64_t *totalsize) { struct udf_node *file_node, *dir_node; struct dirent *dirent; struct stat stat; DIR *dir; char fullsrcpath[1024], fulldstpath[1024]; int error; sprintf(fullsrcpath, "%s/%s", srcprefix, srcname); sprintf(fulldstpath, "%s/%s", dstprefix, dstname); /* stat source file */ bzero(&stat, sizeof(struct stat)); error = lstat(fullsrcpath, &stat); if (error) { error = errno; /* lstat symantics; returns -1 on error */ fprintf(stderr, "Can't stat file/dir \"%s\"! : %s\n", fullsrcpath, strerror(error)); return error; } /* test if `srcname' refers to a directory */ dir = opendir(fullsrcpath); if (dir) { error = udfclient_lookup(parent_node, &dir_node, dstname); if (error) { DEBUG(printf("Create dir %s on UDF\n", fulldstpath)); error = udf_create_directory(parent_node, dstname, &stat, &dir_node); if (error) { closedir(dir); fprintf(stderr, "UDF: couldn't create new directory %s : %s\n", dstname, strerror(error)); return error; } } dir_node->hold++; dirent = readdir(dir); while (dirent) { if (strcmp(dirent->d_name, ".") && strcmp(dirent->d_name, "..")) { /* skip `.' and ',,' */ error = udfclient_put_subtree(dir_node, fullsrcpath, dirent->d_name, fulldstpath, dirent->d_name, totalsize); if (error) break; } dirent = readdir(dir); } closedir(dir); dir_node->hold--; return error; } /* leaf node : prefix is complete name but with `/' prefix */ DEBUG(printf("Put leaf: %s\n", fulldstpath)); error = udfclient_lookup(parent_node, &file_node, dstname); if (!file_node) { error = udf_create_file(parent_node, dstname, &stat, &file_node); if (error) { fprintf(stderr, "UDF: couldn't add new file entry in directory %s for %s : %s\n", dstprefix, dstname, strerror(error)); return error; } } file_node->hold++; error = udfclient_put_file(file_node, fullsrcpath, fulldstpath); file_node->hold--; if (error) fprintf(stderr, "UDF: Couldn't write file %s : %s\n", fulldstpath, strerror(error)); if (error) udf_remove_file(parent_node, file_node, dstname); if (!error) *totalsize += file_node->stat.st_size; return error; } void udfclient_put(int args, char *arg1, char *arg2) { struct udf_node *curdir_node; uint64_t start, now, totalsize, avg_speed; char *source_name, *target_name; int error; if (args > 2) { printf("Syntax: put source [destination]\n"); return; } if (read_only) { printf("Modifying this filingsystem is prevented; use -W flag to enable writing on your own risk!\n"); return; } error = udfclient_lookup_pathname(NULL, &curdir_node, curdir.name); if (error) { printf("Current directory not found?\n"); return; } DEBUG(printf("Attempting to copy %s\n", arg1)); /* determine source and destination entities */ source_name = arg1; target_name = arg1; if (args == 2) target_name = arg2; /* writeout file/dir tree and measure the time/speed */ totalsize = 0; start = getmtime(); error = udfclient_put_subtree(curdir_node, ".", source_name, ".", target_name, &totalsize); now = getmtime(); if (now-start > 0) { avg_speed = (1000000 * totalsize) / (now-start); printf("A total of %d kb transfered at an overal average of %d kb/sec\n", (uint32_t)totalsize/1024, (uint32_t)avg_speed/1024); } else { printf("Transfered %d kb\n", (uint32_t) totalsize/1024); } } /* args start at position 0 of argv */ void udfclient_mput(int args, char **argv) { struct udf_node *curdir_node; uint64_t start, now, totalsize, avg_speed; char *source_name, *target_name; int arg, error; if (args == 0) { printf("Syntax: mput (file | dir)*\n"); return; } if (read_only) { printf("Modifying this filingsystem is prevented; use -W flag to enable writing on your own risk!\n"); return; } error = udfclient_lookup_pathname(NULL, &curdir_node, curdir.name); if (error) { printf("Current directory not found?\n"); return; } /* writeout file/dir trees and measure the time/speed */ totalsize = 0; start = getmtime(); /* process all args */ arg = 0; while (args) { source_name = target_name = argv[arg]; error = udfclient_put_subtree(curdir_node, ".", source_name, ".", target_name, &totalsize); if (error) { fprintf(stderr, "While writing file %s : %s\n", source_name, strerror(error)); break; /* TODO continuation flag? */ } /* advance */ arg++; args--; } now = getmtime(); if (now-start > 0) { avg_speed = (1000000 * totalsize) / (now-start); printf("A total of %d kb transfered at an overal average of %d kb/sec\n", (uint32_t)totalsize/1024, (uint32_t)avg_speed/1024); } else { printf("Transfered %d kb\n", (uint32_t) totalsize/1024); } } void udfclient_trunc(int args, char *arg1, char *arg2) { struct udf_node *udf_node; char *node_name; uint64_t length; int error; if (args != 2) { printf("Syntax: trunc file length\n"); return; } length = strtoll(arg2, NULL, 10); node_name = udfclient_realpath(curdir.name, arg1, NULL); error = udfclient_lookup_pathname(NULL, &udf_node, node_name); if (error || !udf_node) { printf("Can only truncate existing file!\n"); free(node_name); return; } udf_truncate_node(udf_node, length); free(node_name); } void udfclient_sync(void) { struct udf_discinfo *udf_disc; SLIST_FOREACH(udf_disc, &udf_discs_list, next_disc) { udf_sync_disc(udf_disc); } } #define RM_SUBTREE_DIR_BUFFER_SIZE (16*1024) int udfclient_rm_subtree(struct udf_node *parent_node, struct udf_node *dir_node, char *name, char *full_parent_name) { struct uio dir_uio; struct iovec dir_iovec; uint8_t *buffer; uint32_t pos; char *fullpath; struct dirent *dirent; struct stat stat; struct udf_node *entry_node; int eof; int error; if (!dir_node) return ENOENT; error = udfclient_getattr(dir_node, &stat); if (stat.st_mode & S_IFDIR) { buffer = malloc(RM_SUBTREE_DIR_BUFFER_SIZE); if (!buffer) return ENOSPC; /* recurse into this directory */ dir_uio.uio_offset = 0; /* begin at start */ do { dir_iovec.iov_base = buffer; dir_iovec.iov_len = RM_SUBTREE_DIR_BUFFER_SIZE; dir_uio.uio_resid = RM_SUBTREE_DIR_BUFFER_SIZE; dir_uio.uio_iovcnt = 1; dir_uio.uio_iov = &dir_iovec; dir_uio.uio_rw = UIO_WRITE; error = udf_readdir(dir_node, &dir_uio, &eof); pos = 0; while (pos < RM_SUBTREE_DIR_BUFFER_SIZE - dir_uio.uio_resid) { dirent = (struct dirent *) (buffer + pos); /* skip the current node and the parent node */ if ((strcmp(dirent->d_name, ".") && strcmp(dirent->d_name, ".."))) { error = udf_lookup(dir_node, &entry_node, dirent->d_name); if (error) break; error = udfclient_getattr(entry_node, &stat); if (error) break; /* check if the direntry is a directory or a file */ if (stat.st_mode & S_IFDIR) { fullpath = malloc(strlen(full_parent_name) + strlen(dirent->d_name)+2); if (fullpath) { sprintf(fullpath, "%s/%s", full_parent_name, dirent->d_name); error = udfclient_rm_subtree(dir_node, entry_node, dirent->d_name, fullpath); } else { error = ENOMEM; } free(fullpath); } else { error = udf_remove_file(dir_node, entry_node, dirent->d_name); if (!error) printf("rm %s/%s\n", full_parent_name, dirent->d_name); } } if (error) break; pos += sizeof(struct dirent); /* XXX variable size dirents possible XXX */ } } while (!eof); free(buffer); /* leaving directory -> delete directory itself */ if (!error) { error = udf_remove_directory(parent_node, dir_node, name); if (!error) printf("rmdir %s/%s\n", full_parent_name, name); } return error; } return ENOTDIR; } #undef RM_SUBTREE_DIR_BUFFER_SIZE void udfclient_rm(int args, char *argv[]) { struct udf_node *remove_node, *parent_node; struct stat stat; char *target_name, *leaf_name, *full_parent_name; int error, len, arg; if (args == 0) { printf("Syntax: rm (file | dir)*\n"); return; } /* process all args; effectively multiplying an `rm' command */ arg = 0; while (args) { leaf_name = argv[arg]; /* lookup node; target_name gets substituted */ target_name = udfclient_realpath(curdir.name, leaf_name, &leaf_name); error = udfclient_lookup_pathname(NULL, &remove_node, target_name); if (error || !remove_node) { printf("rm %s : %s\n", target_name, strerror(error)); free(target_name); return; /* TODO continuation flag */ /* continue; */ } full_parent_name = udfclient_realpath(target_name, "..", NULL); error = udfclient_lookup_pathname(NULL, &parent_node, full_parent_name); if (error || !parent_node) { printf("rm %s : parent lookup failed : %s\n", target_name, strerror(error)); free(target_name); free(full_parent_name); return; /* TODO continuation flag */ /* continue; */ } error = udfclient_getattr(remove_node, &stat); if (!error) { if (stat.st_mode & S_IFDIR) { len = strlen(target_name); if (target_name[len-1] == '/') target_name[len-1] = '\0'; error = udfclient_rm_subtree(parent_node, remove_node, leaf_name, target_name); } else { error = udf_remove_file(parent_node, remove_node, leaf_name); if (!error) printf("rm %s/%s\n", full_parent_name, leaf_name); } } if (error) fprintf(stderr, "While removing file/dir : %s\n", strerror(error)); free(target_name); free(full_parent_name); if (error) break; /* TODO continuation flag */ /* advance */ arg++; args--; } } void udfclient_mv(int args, char *from, char *to) { struct udf_node *rename_me, *present, *old_parent, *new_parent; char *rename_from_name, *rename_to_name, *old_parent_name, *new_parent_name; int error; if (args != 2) { printf("Syntax: mv source destination\n"); return; } /* `from' gets substituted by its leaf name */ rename_from_name = udfclient_realpath(curdir.name, from, &from); error = udfclient_lookup_pathname(NULL, &rename_me, rename_from_name); if (error || !rename_me) { printf("Can't find file/dir to be renamed\n"); free(rename_from_name); return; } old_parent_name = udfclient_realpath(rename_from_name, "..", NULL); error = udfclient_lookup_pathname(NULL, &old_parent, old_parent_name); if (error || !old_parent) { printf("Can't determine rootdir of renamed file?\n"); free(rename_from_name); free(old_parent_name); return; } /* `to' gets substituted by its leaf name */ rename_to_name = udfclient_realpath(curdir.name, to, &to); udfclient_lookup_pathname(NULL, &present, rename_to_name); new_parent_name = udfclient_realpath(rename_to_name, "..", NULL); error = udfclient_lookup_pathname(NULL, &new_parent, new_parent_name); if (error || !new_parent) { printf("Can't determine rootdir of destination\n"); free(rename_from_name); free(rename_to_name); free(old_parent_name); free(new_parent_name); return; } error = udf_rename(old_parent, rename_me, from, new_parent, present, to); if (error) { printf("Can't move file or directory: %s\n", strerror(error)); return; } free(rename_from_name); free(rename_to_name); free(old_parent_name); free(new_parent_name); } void udfclient_mkdir(int args, char *arg1) { struct stat stat; struct udf_node *udf_node, *parent_node; char *full_create_name, *dirname, *parent_name; int error; if (args != 1) { printf("Syntax: mkdir dir\n"); return; } /* get full name of dir to be created */ full_create_name = udfclient_realpath(curdir.name, arg1, &dirname); parent_name = udfclient_realpath(full_create_name, "..", NULL); error = udfclient_lookup_pathname(NULL, &parent_node, parent_name); if (error || !parent_node) { printf("Can't determine directory the new directory needs to be created in\n"); free(full_create_name); free(parent_name); return; } bzero(&stat, sizeof(struct stat)); stat.st_uid = UINT_MAX; stat.st_gid = UINT_MAX; stat.st_mode = 0755 | S_IFDIR; /* dont forget this! */ error = udf_create_directory(parent_node, dirname, &stat, &udf_node); if (error) { printf("Can't create directory %s : %s\n", arg1, strerror(error)); } free(full_create_name); free(parent_name); } /* `line' gets more and more messed up in the proces */ char *udfclient_get_one_arg(char *line, char **result) { char chr, limiter, *end_arg; *result = NULL; /* get prepending whitespace */ while (*line && (*line <= ' ')) line++; limiter = ' '; if (*line == '"') { line++; limiter = '"'; } *result = line; while (*line) { chr = *line; if (chr && (chr < ' ')) chr = ' '; if (chr == 0 || chr == limiter) { break; } else { *line = chr; } line++; } end_arg = line; /* get appended whitespace */ while (*line && (*line <= ' ')) line++; *end_arg = 0; return line; } int udfclient_get_args(char *line, char *argv[]) { int arg, args; for (arg = 0; arg < MAX_ARGS+1; arg++) { argv[arg] = ""; } /* get all arguments */ args = 0; while (args < MAX_ARGS+1) { line = udfclient_get_one_arg(line, &argv[args]); args++; if (!*line) { return args; } } printf("UDFclient implementation limit: too many arguments\n"); return 0; } void udfclient_interact(void) { int args, params; char *cmd; char *argv[MAX_ARGS+1]; char line[4096]; udfclient_pwd(0); while (1) { printf("UDF> "); clearerr(stdin); *line = 0; fgets(line, 4096, stdin); if ((*line == 0) && feof(stdin)) { printf("quit\n"); return; } args = udfclient_get_args(line, argv); cmd = argv[0]; params = args -1; if (args) { if (strcmp(cmd, "")==0) continue; if (strcmp(cmd, "ls")==0) { udfclient_ls(params, argv[1]); continue; } if (strcmp(cmd, "cd")==0) { udfclient_cd(params, argv[1]); continue; } if (strcmp(cmd, "lcd")==0) { udfclient_lcd(params, argv[1]); continue; } if (strcmp(cmd, "lls")==0) { udfclient_lls(params); continue; } if (strcmp(cmd, "pwd")==0) { udfclient_pwd(params); continue; } if (strcmp(cmd, "free")==0) { udfclient_free(params); continue; } if (strcmp(cmd, "get")==0) { udfclient_get(params, argv[1], argv[2]); continue; } if (strcmp(cmd, "mget")==0) { udfclient_mget(params, argv + 1); continue; } if (strcmp(cmd, "put")==0) { /* can get destination file/dir (one day) */ udfclient_put(params, argv[1], argv[2]); continue; } if (strcmp(cmd, "mput")==0) { udfclient_mput(params, argv + 1); continue; } if (strcmp(cmd, "trunc")==0) { udfclient_trunc(params, argv[1], argv[2]); continue; } if (strcmp(cmd, "mkdir")==0) { udfclient_mkdir(params, argv[1]); continue; } if (strcmp(cmd, "rm")==0) { udfclient_rm(params, argv + 1); continue; } if (strcmp(cmd, "mv")==0) { udfclient_mv(params, argv[1], argv[2]); continue; } if (strcmp(cmd, "sync")==0) { udfclient_sync(); continue; } if (strcmp(cmd, "help")==0) { printf("Selected commands available (use \" pair for filenames with spaces) :\n" "ls [file | dir]\tlists the UDF directory\n" "cd [dir]\t\tchange current UDF directory\n" "lcd [dir]\t\tchange current directory\n" "lls\t\t\tlists current directory\n" "pwd\t\t\tdisplay current directories\n" "free\t\t\tdisplay free space on disc\n" "get source [dest]\tretrieve a file / directory from disc\n" "mget (file | dir)*\tretrieve set of files / directories\n" "put source [dest]\twrite a file / directory to disc\n" "mput (file | dir)*\twrite a set of files / directories\n" "trunc file length\ttrunc file to length\n" "mkdir dir\t\tcreate directory\n" "rm (file | dir)*\tdelete set of files / directories\n" "mv source dest\t\trename a file (limited)\n" "sync\t\t\tsync filingsystem\n" "quit\t\t\texits program\n" "exit\t\t\talias for quit\n" ); continue; } if (strcmp(cmd, "quit")==0 || strcmp(cmd, "exit")==0) { return; } printf("\nUnknown command %s\n", cmd); } } } int usage(char *program) { fprintf(stderr, "Usage: %s [options] devicename [devicename]*)\n", program); fprintf(stderr, "-u level UDF system verbose level\n" "-r range use only selected sessions like -3,5,7 or 6-\n" "-W allow writing (temporary flag)\n" "-F force mount writable when marked dirty (use with cause)\n" "-b blocksize use alternative sectorsize; use only on files/discs\n" "-D debug/verbose SCSI command errors\n" ); return 1; } extern char *optarg; extern int optind; extern int optreset; int main(int argc, char *argv[]) { struct udf_discinfo *disc, *next_disc; uint32_t alt_sector_size; char *progname, *range; int flag, mnt_flags; int error; progname = argv[0]; if (argc == 1) { return usage(progname); } /* be a bit more verbose */ udf_verbose = UDF_VERBLEV_ACTIONS; uscsilib_verbose= 0; mnt_flags = UDF_MNT_RDONLY; range = NULL; alt_sector_size = 0; while ((flag = getopt(argc, argv, "u:Dr:WFb:")) != -1) { switch (flag) { case 'u' : udf_verbose = atoi(optarg); break; case 'D' : uscsilib_verbose = 1; break; case 'r' : range = strdup(optarg); if (udf_check_session_range(range)) { fprintf(stderr, "Invalid range %s\n", range); return usage(progname); } break; case 'W' : mnt_flags &= ~UDF_MNT_RDONLY; break; case 'F' : mnt_flags |= UDF_MNT_FORCE; break; case 'b' : alt_sector_size = atoi(optarg); break; default : return usage(progname); } } argv += optind; argc -= optind; if (argc == 0) return usage(progname); if (!(mnt_flags & UDF_MNT_RDONLY)) { printf("--------------------------------\n"); printf("WARNING: writing enabled, use on own risc\n"); printf("\t* DONT cancel program or data-loss might occure\n"); printf("\t* set dataspace userlimits very high when writing thousands of files\n"); printf("\nEnjoy your writing!\n"); printf("--------------------------------\n\n\n"); printf("%c", 7); fflush(stdout); sleep(1); printf("%c", 7); fflush(stdout); sleep(1); printf("%c", 7); fflush(stdout); } /* all other arguments are devices */ SLIST_INIT(&udf_discs_list); while (argc) { printf("Opening device %s\n", *argv); error = udf_mount_disc(*argv, range, alt_sector_size, mnt_flags, &disc); if (error) { fprintf(stderr, "Can't open my device; bailing out : %s\n", strerror(error)); exit(1); } if (read_only) disc->recordable = 0; if (read_only) disc->rewritable = 0; argv++; argc--; if (udf_verbose) printf("\n\n"); } printf("\n"); printf("Resulting list of alive sets :\n\n"); udf_dump_alive_sets(); /* interactive part */ bzero(&curdir, sizeof(struct curdir)); curdir.mountpoint = NULL; curdir.name = strdup("/"); udfclient_ls(0, ""); udfclient_interact(); /* close part */ printf("Closing discs\n"); disc = SLIST_FIRST(&udf_discs_list); while (disc) { next_disc = SLIST_NEXT(disc, next_disc); udf_dismount_disc(disc); disc = next_disc; } return 0; }