/* Copyright (C) 2004-2006, 2008, 2012-2014, 2017 Rocky Bernstein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* ISO Info - prints various information about a ISO 9660 image. */ #include "getopt.h" #include "util.h" #undef err_exit #define err_exit(fmt, args...) \ report (stderr, "%s: "fmt, program_name, ##args); \ iso9660_close(p_iso); \ if (NULL != program_name) free(program_name); \ if (NULL != source_name) free(source_name); \ return(EXIT_FAILURE); #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #include #include #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_ALLOCA_H #include #endif #if 0 #define STRONG "\033[1m" #define NORMAL "\033[0m" #else #define STRONG "__________________________________\n" #define NORMAL "" #endif /* TODO: Find a better place from where cd-info can read it too. */ /* ECMA-119 allows only a depth of 8 directories. Nobody obeys. Rock Ridge allows path length 1023. This would be max depth 512. */ #define CDIO_MAX_DIR_RECURSION 512 /* Used by `main' to communicate with `parse_opt'. And global options */ static struct arguments { uint32_t debug_level; int version_only; int silent; int no_header; int no_joliet; int no_xa; int no_rock_ridge; int print_iso9660; int print_udf; int print_iso9660_short; int64_t show_rock_ridge; } opts; /* Configuration option codes */ enum { OP_HANDLED = 0, OP_USAGE, /* These are the remaining configuration options */ OP_VERSION, }; /* Parse a all options. */ static bool parse_options (int argc, char *argv[]) { int opt; int rc = EXIT_FAILURE; static const char helpText[] = "Usage: %s [OPTION...]\n" " -d, --debug=UINT Set debugging to LEVEL\n" " -i, --input[=FILE] Filename to read ISO-9960 image from\n" " -f Generate output similar to 'find . -print'\n" " -l, --iso9660 output similar to 'ls -lR' for an ISO 9660 fs\n" " -U, --udf output similar to 'ls -lR' for a UDF fs\n" " --no-header Don't display header and copyright (for regression\n" #ifdef HAVE_JOLIET " --no-joliet Don't use Joliet-extension information\n" #endif /*HAVE_JOLIET*/ " --no-rock-ridge Don't use Rock-Ridge-extension information\n" " --no-xa Don't use XA-extension information\n" " -r --show-rock-ridge UINT Show if image uses Rock-Ridge extensions\n" " A maximum of UINT files will be considered.\n" " Use 0 for all files.\n" " -q, --quiet Don't produce warning output\n" " -V, --version display version and copyright information and exit\n" "\n" "Help options:\n" " -?, --help Show this help message\n" " --usage Display brief usage message\n"; static const char usageText[] = "Usage: %s [-i|--input FILE] [-f] [-l|--iso9660] [-U|--udf]\n" " [--no-header] [--no-joliet] [--no-rock-ridge] [--show-rock-ridge] [--no-xa] [-q|--quiet]\n" " [-d|--debug INT] [-V|--version] [-?|--help] [--usage]\n"; static const char optionsString[] = "d:i::flUqV?"; static const struct option optionsTable[] = { {"debug", required_argument, NULL, 'd'}, {"input", optional_argument, NULL, 'i'}, {"iso9660", no_argument, NULL, 'l'}, {"udf", no_argument, NULL, 'U'}, {"no-header", no_argument, &opts.no_header, 1 }, #ifdef HAVE_JOLIET {"no-joliet", no_argument, &opts.no_joliet, 1 }, #endif /*HAVE_JOLIET*/ {"no-rock-ridge", no_argument, &opts.no_rock_ridge, 1 }, {"no-xa", no_argument, &opts.no_xa, 1 }, {"quiet", no_argument, NULL, 'q'}, {"show-rock-ridge", required_argument, NULL, 'r' }, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, '?' }, {"usage", no_argument, NULL, OP_USAGE }, { NULL, 0, NULL, 0 } }; program_name = strrchr(argv[0],'/'); program_name = program_name ? strdup(program_name+1) : strdup(argv[0]); while ((opt = getopt_long(argc, argv, optionsString, optionsTable, NULL)) >= 0) { switch (opt) { case 'd': opts.debug_level = atoi(optarg); break; case 'i': if (optarg != NULL) { if (NULL != source_name) free(source_name); source_name = strdup(optarg); break; } case 'f': opts.print_iso9660_short = 1; break; case 'l': opts.print_iso9660 = 1; break; case 'U': opts.print_udf = 1; break; case 'q': opts.silent = 1; break; case 'r': opts.show_rock_ridge = atoll(optarg); break; case 'V': opts.version_only = 1; break; case '?': fprintf(stdout, helpText, program_name); rc = EXIT_INFO; goto error_exit; case OP_USAGE: fprintf(stderr, usageText, program_name); rc = EXIT_INFO; goto error_exit; case OP_HANDLED: break; } } if (optind < argc) { const char *remaining_arg = argv[optind++]; if ( optind < argc ) { report( stderr, "%s: Source specified in previously %s and %s\n", program_name, source_name, remaining_arg ); goto error_exit; } if (NULL != source_name) free(source_name); source_name = strdup(remaining_arg); } return true; error_exit: free(program_name); exit(rc); } /* CDIO logging routines */ static void _log_handler (cdio_log_level_t level, const char message[]) { if (level == CDIO_LOG_DEBUG && opts.debug_level < 2) return; if (level == CDIO_LOG_INFO && opts.debug_level < 1) return; if (level == CDIO_LOG_WARN && opts.silent) return; if (level == CDIO_LOG_ERROR) { // print an error like default, but *don't* exit. fprintf (stderr, "**ERROR: %s\n", message); fflush (stderr); return; } gl_default_cdio_log_handler (level, message); } static void print_iso9660_recurse (iso9660_t *p_iso, const char psz_path[], unsigned int rec_counter) { CdioISO9660FileList_t *entlist; CdioISO9660DirList_t *p_dirlist = iso9660_dirlist_new(); CdioListNode_t *entnode; uint8_t i_joliet_level = iso9660_ifs_get_joliet_level(p_iso); char *translated_name = (char *) alloca(4096); size_t translated_name_size = 4096; entlist = iso9660_ifs_readdir (p_iso, psz_path); if (opts.print_iso9660) { printf ("%s:\n", psz_path); } if (NULL == entlist) { iso9660_dirlist_free(p_dirlist); report( stderr, "Error getting above directory information\n" ); return; } rec_counter++; if (rec_counter > CDIO_MAX_DIR_RECURSION) { iso9660_dirlist_free(p_dirlist); iso9660_filelist_free(entlist); report( stderr, "Directory recursion too deep. ISO most probably damaged.\n" ); return; } /* Iterate over files in this directory */ _CDIO_LIST_FOREACH (entnode, entlist) { iso9660_stat_t *p_statbuf = _cdio_list_node_data (entnode); char *psz_iso_name = p_statbuf->filename; char _fullname[4096] = { 0, }; if (strlen(psz_iso_name) >= translated_name_size) { translated_name_size = strlen(psz_iso_name)+1; } if (yep != p_statbuf->rr.b3_rock || 1 == opts.no_rock_ridge) { iso9660_name_translate_ext(psz_iso_name, translated_name, i_joliet_level); snprintf (_fullname, sizeof (_fullname), "%s%s", psz_path, translated_name); } else { snprintf (_fullname, sizeof (_fullname), "%s%s", psz_path, psz_iso_name); } strncat (_fullname, "/", sizeof(_fullname) - strlen(_fullname) - 1); if (p_statbuf->type == _STAT_DIR && strcmp (psz_iso_name, ".") && strcmp (psz_iso_name, "..")) _cdio_list_append (p_dirlist, strdup (_fullname)); if (opts.print_iso9660) { print_fs_attrs(p_statbuf, 0 == opts.no_rock_ridge, iso9660_ifs_is_xa(p_iso) && 0 == opts.no_xa, psz_iso_name, translated_name); } else { if ( strcmp (psz_iso_name, ".") && strcmp (psz_iso_name, "..")) printf("%9u %s%s\n", (unsigned int) p_statbuf->size, psz_path, yep == p_statbuf->rr.b3_rock ? psz_iso_name : translated_name); } } iso9660_filelist_free(entlist); if (opts.print_iso9660) { printf ("\n"); } /* Now recurse over the directories. */ _CDIO_LIST_FOREACH (entnode, p_dirlist) { char *_fullname = _cdio_list_node_data (entnode); print_iso9660_recurse (p_iso, _fullname, rec_counter); } iso9660_dirlist_free(p_dirlist); } static void print_iso9660_fs (iso9660_t *iso) { print_iso9660_recurse (iso, "/", 0); } static void print_udf_file_info(const udf_dirent_t *p_udf_dirent, const char* psz_dirname, const char *psz_filename) { time_t mod_time = udf_get_modification_time(p_udf_dirent); char date_str[30]; char psz_mode[11]="invalid"; const char *psz_fname= psz_filename ? psz_filename : udf_get_filename(p_udf_dirent); if (!opts.print_iso9660) { if (strcmp(psz_dirname, ".") != 0) { printf("%9lu ", (long unsigned int) udf_get_file_length(p_udf_dirent)); printf("/%s", psz_dirname); printf("%s\n", *psz_fname ? psz_fname : "/"); } return; } /* Print directory attributes*/ printf("%s ", udf_mode_string(udf_get_posix_filemode(p_udf_dirent), psz_mode)); /* fake uid/gid */ printf("0 0 "); printf("%3d ", udf_get_link_count(p_udf_dirent)); printf("%9lu ", (long unsigned int) udf_get_file_length(p_udf_dirent)); strftime(date_str, sizeof(date_str), "%b %d %Y %H:%M:%S ", localtime(&mod_time)); printf("%s %s", date_str, *psz_fname ? psz_fname : "/"); printf("\n"); } static void list_udf_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const char *psz_path) { if (!p_udf_dirent) return; if (opts.print_iso9660) { printf ("\n/%s:\n", psz_path); } print_udf_file_info(p_udf_dirent, psz_path, "."); while (udf_readdir(p_udf_dirent)) { if (udf_is_dir(p_udf_dirent)) { udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent); if (p_udf_dirent2) { const char *psz_dirname = udf_get_filename(p_udf_dirent); const unsigned int i_newlen=2 + strlen(psz_path) + strlen(psz_dirname); char *psz_newpath = calloc(1, sizeof(char)*i_newlen); snprintf(psz_newpath, i_newlen, "%s%s/", psz_path, psz_dirname); list_udf_files(p_udf, p_udf_dirent2, psz_newpath); free(psz_newpath); } } else { print_udf_file_info(p_udf_dirent, psz_path, NULL); } } } static int print_udf_fs (void) { udf_t *p_udf; p_udf = udf_open (source_name); if (NULL == p_udf) { fprintf(stderr, "Sorry, couldn't open %s as something using UDF\n", source_name); return 1; } else { udf_dirent_t *p_udf_root = udf_get_root(p_udf, true, 0); if (NULL == p_udf_root) { fprintf(stderr, "Sorry, couldn't find / in %s\n", source_name); return 1; } list_udf_files(p_udf, p_udf_root, ""); } udf_close(p_udf); return 0; } /* Initialize global variables. */ static void init(void) { gl_default_cdio_log_handler = cdio_log_set_handler (_log_handler); /* Default option values. */ opts.silent = false; opts.no_header = false; opts.no_joliet = 0; opts.no_rock_ridge = 0; opts.no_xa = 0; opts.debug_level = 0; opts.print_iso9660 = 0; opts.print_iso9660_short = 0; opts.show_rock_ridge = -1; } #define print_vd_info(title, fn) \ if (fn(p_iso, &psz_str)) { \ printf(title ": %s\n", psz_str); \ } \ free(psz_str); \ psz_str = NULL; /* ------------------------------------------------------------------------ */ int main(int argc, char *argv[]) { iso9660_t *p_iso=NULL; iso_extension_mask_t iso_extension_mask = ISO_EXTENSION_ALL; init(); /* Parse our arguments; every option seen by `parse_opt' will be reflected in `arguments'. */ parse_options(argc, argv); print_version(program_name, CDIO_VERSION, opts.no_header, opts.version_only); if (opts.debug_level == 3) { cdio_loglevel_default = CDIO_LOG_INFO; } else if (opts.debug_level >= 4) { cdio_loglevel_default = CDIO_LOG_DEBUG; } if (source_name==NULL) { err_exit("No input device given/found%s\n", ""); } if (opts.no_joliet) { iso_extension_mask &= ~ISO_EXTENSION_JOLIET; } p_iso = iso9660_open_ext (source_name, iso_extension_mask); if (p_iso==NULL) { err_exit("Error in opening ISO-9660 image%s\n", ""); } if (opts.silent == 0) { char *psz_str = NULL; uint8_t u_joliet_level = iso9660_ifs_get_joliet_level(p_iso); printf(STRONG "ISO 9660 image: %s\n", source_name); print_vd_info("Application ", iso9660_ifs_get_application_id); print_vd_info("Preparer ", iso9660_ifs_get_preparer_id); print_vd_info("Publisher ", iso9660_ifs_get_publisher_id); print_vd_info("System ", iso9660_ifs_get_system_id); print_vd_info("Volume ", iso9660_ifs_get_volume_id); print_vd_info("Volume Set ", iso9660_ifs_get_volumeset_id); if (opts.show_rock_ridge >= 0) { switch (iso9660_have_rr(p_iso, (uint64_t) opts.show_rock_ridge)) { case yep: printf("Rock Ridge : yes\n"); break; case nope: printf("Rock Ridge : no\n"); break; case dunno: printf("Rock Ridge : possibly not\n"); } } if (u_joliet_level == 0) { printf("No Joliet extensions\n"); } else { printf("Joliet Level: %u\n", u_joliet_level); } } if (opts.print_iso9660 || opts.print_iso9660_short) { printf(STRONG "ISO-9660 Information\n" NORMAL); if (opts.print_iso9660 && opts.print_iso9660_short) { printf("Note: both -f and -l options given -- " "-l (long listing) takes precidence\n"); } print_iso9660_fs(p_iso); } else if (opts.print_udf) { print_udf_fs(); } free(source_name); iso9660_close(p_iso); /* Not reached:*/ free(program_name); return(EXIT_SUCCESS); }