#include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include "s1kd_tools.h" #define PROG_NAME "s1kd-sns" #define VERSION "1.8.0" #define ERR_PREFIX PROG_NAME ": ERROR: " #define E_ENCODING_ERROR ERR_PREFIX "Error encoding path name.\n" #define EXIT_ENCODING_ERROR 1 #define EXIT_OS_ERROR 2 #define EXIT_NO_BREX 3 #define DEFAULT_SNS_DNAME "SNS" static int hlink(const char *path, const char *fname) { #ifdef _WIN32 return CreateHardLink(fname, path, 0); #else return link(path, fname); #endif } static int slink(const char *path, const char *fname) { #ifdef _WIN32 return CreateSymbolicLink(fname, path, 0); #else return symlink(path, fname); #endif } /* The type of link to use, hard or soft (symbolic). */ static int (*linkfn)(const char *path, const char *fname) = hlink; /* Title SNS directories using only the SNS code, not including the SNS title. */ static bool only_numb = false; static void change_dir(const char *dir) { if (chdir(dir) != 0) { fprintf(stderr, ERR_PREFIX "Cannot change directory to %s: %s\n", dir, strerror(errno)); exit(EXIT_OS_ERROR); } } static void rename_dir(const char *old, const char *new) { if (rename(old, new) != 0) { fprintf(stderr, ERR_PREFIX "Cannot rename directory %s to %s: %s\n", old, new, strerror(errno)); exit(EXIT_OS_ERROR); } } static void get_current_dir(char *buf, size_t size) { if (!getcwd(buf, size)) { fprintf(stderr, ERR_PREFIX "Cannot get current directory: %s\n", strerror(errno)); exit(EXIT_OS_ERROR); } } /* Indent printed SNS entry to a specified level. */ static void indent(int level) { int i; for (i = 0; i < level * 4; ++i) putchar(' '); } /* Print the SNS. */ static void print_sns(xmlNodePtr node, int level) { xmlNodePtr cur; char *content; for (cur = node->children; cur; cur = cur->next) { if (xmlStrcmp(cur->name, BAD_CAST "snsCode") == 0) { content = (char *) xmlNodeGetContent(cur); indent(level); printf("%s", content); xmlFree(content); } else if (xmlStrcmp(cur->name, BAD_CAST "snsTitle") == 0) { content = (char *) xmlNodeGetContent(cur); printf(" - %s\n", content); xmlFree(content); } else if (cur->type == XML_ELEMENT_NODE) { print_sns(cur, level + 1); } } } /* Replace characters that cannot be used in directory names. */ static void cleanstr(char *s) { int i; for (i = 0; s[i]; ++i) { switch(s[i]) { case '/': #ifdef _WIN32 case '<': case '>': case ':': case '"': case '\\': case '|': case '?': case '*': #endif s[i] = ' '; } } } /* Create a directory if it does not exist. */ static void makedir(const char *path) { if (access(path, F_OK) == -1) { #ifdef _WIN32 mkdir(path); #else mkdir(path, S_IRWXU); #endif } } /* Create the directory structure for the SNS. */ static void setup_sns(xmlNodePtr node, const char *snsdname) { xmlNodePtr cur; char *content; char code[256]; for (cur = node->children; cur; cur = cur->next) { if (xmlStrcmp(cur->name, BAD_CAST "snsCode") == 0) { content = (char *) xmlNodeGetContent(cur); strcpy(code, content); xmlFree(content); makedir(code); change_dir(code); } else if (xmlStrcmp(cur->name, BAD_CAST "snsTitle") == 0) { char oldname[PATH_MAX], newname[PATH_MAX]; if (only_numb) continue; if (snprintf(oldname, PATH_MAX, "%s", code) < 0) { fprintf(stderr, E_ENCODING_ERROR); exit(EXIT_ENCODING_ERROR); } content = (char *) xmlNodeGetContent(cur); cleanstr(content); if (snprintf(newname, PATH_MAX, "%s - %s", code, content) < 0) { fprintf(stderr, E_ENCODING_ERROR); exit(EXIT_ENCODING_ERROR); } xmlFree(content); change_dir(".."); rename_dir(oldname, newname); change_dir(newname); } else if (cur->type == XML_ELEMENT_NODE) { setup_sns(cur, snsdname); } } change_dir(".."); } /* Tests if the given file is a data module. */ static int is_dmodule(const char *fname) { return (strncmp(fname, "DMC-", 4) == 0 || strncmp(fname, "DME-", 4) == 0) && strncasecmp(fname + (strlen(fname) - 4), ".XML", 4) == 0; } /* Convenient structure for DM code properties. */ struct dm_code { char model_ident_code[15]; char system_diff_code[5]; char system_code[4]; char sub_system_code[2]; char sub_sub_system_code[2]; char assy_code[5]; char disassy_code[3]; char disassy_code_variant[4]; char info_code[4]; char info_code_variant[2]; char item_location_code[2]; char learn_code[4]; char learn_event_code[2]; }; /* Read a DM code from a given string. */ static int parse_dmcode(struct dm_code *code, const char *str) { int c; c = sscanf(str, "%*[^-]-%14[^-]-%4[^-]-%3[^-]-%1s%1s-%4[^-]-%2s%3[^-]-%3s%1s-%1s-%3s%1s", code->model_ident_code, code->system_diff_code, code->system_code, code->sub_system_code, code->sub_sub_system_code, code->assy_code, code->disassy_code, code->disassy_code_variant, code->info_code, code->info_code_variant, code->item_location_code, code->learn_code, code->learn_event_code); if (c != 11 && c != 13) return 1; return 0; } /* Test if the SNS directory exists for a specified code. */ static int sns_exists(const char *code, char *dname) { DIR *dir; struct dirent *cur; int exists = 0; dir = opendir("."); while ((cur = readdir(dir))) { if (strncmp(code, cur->d_name, strlen(code)) == 0) { exists = 1; strcpy(dname, cur->d_name); break; } } closedir(dir); return exists; } /* Place a link to a DM file in to the proper place in the SNS directory hierarchy. */ static void placedm(const char *fname, struct dm_code *code, const char *snsdname, const char *srcdname) { char path[PATH_MAX], dname[PATH_MAX], orig[PATH_MAX]; bool link = true; get_current_dir(orig, PATH_MAX); strcpy(path, srcdname); strcat(path, "/"); change_dir(snsdname); if (sns_exists(code->system_code, dname)) { change_dir(dname); if (sns_exists(code->sub_system_code, dname)) { change_dir(dname); if (sns_exists(code->sub_sub_system_code, dname)) { change_dir(dname); if (sns_exists(code->assy_code, dname)) { change_dir(dname); } } } } strcat(path, fname); if (access(fname, F_OK) != -1) { char d[PATH_MAX]; get_current_dir(d, PATH_MAX); if (strcmp(d, srcdname) != 0) { unlink(fname); } else { link = false; } } if (link && linkfn(path, fname) != 0) { fprintf(stderr, ERR_PREFIX "%s: %s => %s\n", strerror(errno), path, fname); exit(EXIT_OS_ERROR); } change_dir(orig); } /* Resort DMs in to the SNS directory hierarchy. */ static void sort_sns(const char *snsdname, const char *srcdname) { DIR *dir; struct dirent *cur; dir = opendir(srcdname); while ((cur = readdir(dir))) { if (is_dmodule(cur->d_name)) { struct dm_code code; if (parse_dmcode(&code, cur->d_name) == 0) { placedm(cur->d_name, &code, snsdname, srcdname); } } } closedir(dir); } /* Print or setup the SNS directory structure for a given BREX containing SNS rules. */ static void print_or_setup_sns(const char *brex_fname, bool printsns, const char *snsdname, const char *srcdname) { xmlDocPtr brex; xmlXPathContextPtr ctxt; xmlXPathObjectPtr results; if (!(brex = read_xml_doc(brex_fname))) { fprintf(stderr, ERR_PREFIX "Could not read BREX data module: %s\n", brex_fname); exit(EXIT_NO_BREX); } ctxt = xmlXPathNewContext(brex); results = xmlXPathEvalExpression(BAD_CAST "//snsRules/snsDescr", ctxt); if (!xmlXPathNodeSetIsEmpty(results->nodesetval)) { xmlNodePtr sns_descr; sns_descr = results->nodesetval->nodeTab[0]; if (printsns) { print_sns(sns_descr, -1); } else if (access(snsdname, F_OK) == -1) { char cwd[PATH_MAX]; get_current_dir(cwd, PATH_MAX); makedir(snsdname); change_dir(snsdname); setup_sns(sns_descr, snsdname); change_dir(cwd); } if (!printsns) { sort_sns(snsdname, srcdname); } } xmlXPathFreeObject(results); xmlXPathFreeContext(ctxt); xmlFreeDoc(brex); } static void show_help(void) { puts("Usage: " PROG_NAME " [-D ] [-d ] [-cmnpsh?] [ ...]"); puts(""); puts("Options:"); puts(" -c, --copy Copy files instead of linking."); puts(" -D, --srcdir Directory where DMs are stored. Default is current directory."); puts(" -d, --outdir Directory to organize DMs in to. Default is \"" DEFAULT_SNS_DNAME "\""); puts(" -h, -?, --help Show usage message."); puts(" -m, --move Move files instead of linking."); puts(" -n, --only-code Only use the SNS code to name directories."); puts(" -p, --print Print SNS instead of organizing."); puts(" -s, --symlink Use symbolic links."); puts(" --version Show version information."); puts(" BREX data module to read SNS structure from."); LIBXML2_PARSE_LONGOPT_HELP } static void show_version(void) { printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION); printf("Using libxml %s\n", xmlParserVersion); } int main(int argc, char **argv) { int i; bool printsns = false; char *snsdname = NULL; char *srcdname = NULL; const char *sopts = "cD:d:mnpsh?"; struct option lopts[] = { {"version" , no_argument , 0, 0}, {"help" , no_argument , 0, 'h'}, {"copy" , no_argument , 0, 'c'}, {"srcdir" , required_argument, 0, 'D'}, {"outdir" , required_argument, 0, 'd'}, {"move" , no_argument , 0, 'm'}, {"only-code", no_argument , 0, 'n'}, {"print" , no_argument , 0, 'p'}, {"symlink" , no_argument , 0, 's'}, LIBXML2_PARSE_LONGOPT_DEFS {0, 0, 0, 0} }; int loptind = 0; srcdname = malloc(PATH_MAX + 1); get_current_dir(srcdname, PATH_MAX); while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) { switch (i) { case 0: if (strcmp(lopts[loptind].name, "version") == 0) { show_version(); return 0; } LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg) break; case 'c': linkfn = copy; break; case 'D': real_path(optarg, srcdname); break; case 'd': snsdname = strdup(optarg); break; case 's': linkfn = slink; break; case 'm': linkfn = rename; break; case 'n': only_numb = true; break; case 'p': printsns = true; break; case 'h': case '?': show_help(); return 0; } } if (!snsdname) { snsdname = strdup(DEFAULT_SNS_DNAME); } if (optind < argc) { for (i = optind; i < argc; ++i) { print_or_setup_sns(argv[i], printsns, snsdname, srcdname); } } else { print_or_setup_sns("-", printsns, snsdname, srcdname); } free(snsdname); free(srcdname); xmlCleanupParser(); return 0; }