par-cmdline/0040755000076500007650000000000007401007025012626 5ustar willemwillempar-cmdline/AUTHORS0100644000076500007650000000004107366423412013702 0ustar willemwillemWillem Monsuwe par-cmdline/COPYING0100644000076500007650000004311007366423412013671 0ustar willemwillem GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. par-cmdline/Makefile0100644000076500007650000000044207375511330014274 0ustar willemwillem CFLAGS=-g -W -Wall -Wno-unused -O2 par: backend.o checkpar.o makepar.o rwpar.o rs.o md5.o fileops.o main.o readoldpar.o interface.o ui_text.o $(CC) -o $@ $^ clean: rm -f core par par.exe *.o all: par par.exe: make clean make CC="dos-gcc -s" install: par install par ${HOME}/bin/ par-cmdline/NEWS0100644000076500007650000000071007366423412013334 0ustar willemwillem New in v0.9: - Complies to v1.0 spec - Reads old par files as well (v0.2 spec, v0.9 spec) New in v0.5: - More error checking, deletes failed output files - New command (par mix): Read all parity archives in the current directory, and combine all of them to try to recover as many files as possible. New in v0.4: - Complies to v0.9 file spec. - Control hash implemented. - The ability to not include certain files in the .pxx files (+i flag) par-cmdline/README0100644000076500007650000000676607366423412013536 0ustar willemwillem Parity Archive - A way to restore missing files in a set. Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) The source code can be downloaded from parchive.sourceforge.net as par-v0.2alpha.tar.gz This version is released under the GNU Public License. See the file COPYING (in the source code archive) for details. File format by Stefan Wehlus - initial idea by Tobias Rieper, further suggestions by Kilroy Balore Beta release, version 0.9 DESCRIPTION: This version uses Reed-Solomon coding to make multiple parity volumes from the same set of files. What this means is that you can recover as many files as you have recovery volumes. Any recovery volumes for that set will do. TYPICAL EXAMPLE OF USE: You are uploading a rar-archive, Archive.rar to a newsgroup, split into 40 files. You decide to make a parity archive with five recovery volumes: >par add -n5 Archive.PAR Archive.r?? You machine rattles for a minute or two, resulting in one (small) Archive.PAR file, and five files Archive.P01 - P05. You upload these. People download your files, and the Archive.PAR file to check if they downloaded correctly. Someone runs par to check if he got all files: >par check Archive.PAR It reports that three files are missing, and he would need three parity volumes to recover them. He tries to download these, and he gets Archive.P02, .P04 and .P05. Then he runs par to recover those files: >par recover Archive.PAR His machine rattles for a minute or two, and all his files are restored. HINT FOR WINDOWS USERS: If you make a file 'par.bat' which contains the following line: par.exe r -- %1 You can associate this with .par files, so it will automatically recover files from the .par file you doubleclick on. NOTES: - This version of par never overwrites an existing file. It can, however, move files out of the way (with the -m flag) - Par will also search for files with different filenames (by the md5 checksum), and can give those files their correct name (with -f) Searching for missing files could take some time, if there are lots of files in the directory - Par will only work with files in the current directory. - Par can try to recover files using parity volumes from different sources: >par m This has a pretty decent chance of working, but it's not guaranteed to work. It will try to locate every single parity volume in the current directory, throwing them together to try to recover as many files from them as possible. If you use this in a large dir, Par might try to open too many files and fail. REED-SOLOMON CODING: The file rs.doc contains sort of an explanation on how the encoding works. Also, all Reed-Solomon related stuff is in one file, rs.c, with a single interface function. This should make it easy to write different RS-based recovery archive programs. You can look at the following link: ``A Tutorial on Reed-Solomon Coding for Fault-Tolerance in RAID-like Systems'' TODO: - Write a better explanation of how Reed-Solomon coding works (see rs.doc) - Check if it compiles on something like a PPC (non-aligned access issues) COMPILING: You're pretty much on your own here. There's a small Makefile, and the code uses as few special things as possible. GCC should be able to compile it on most Unices. It also compiles for MS-DOS, using DJGPP (http://www.delorie.com/djgpp/) and it's been tested on a Win98SE box. As this is a testing version, and the spec is going to change soon, not much effort has been made for portability. par-cmdline/checkpar.c0100644000076500007650000000346107375542021014565 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Check PAR files and restore missing files. \*/ #include #include #include #include #include #include "util.h" #include "backend.h" #include "rwpar.h" #include "checkpar.h" #include "fileops.h" /*\ |*| Check the data files in a PXX file, and maybe restore missing files \*/ int check_par(par_t *par) { int m; pfile_t *p; u16 *str; sub_t *sub = 0; /*\ Look for all the data files \*/ for (m = 0, p = par->files; p; p = p->next) { if (!find_file(p, 1) && USE_FILE(p)) m++; } if (cmd.smart) sub = find_best_sub(par->files, 2); if (cmd.fix) { for (p = par->files; p; p = p->next) { if (!find_file(p, 0)) continue; str = do_sub(p->filename, sub); if (!unicode_cmp(str, p->match->filename)) continue; rename_away(p->match->filename, str); } } if (m == 0) { fprintf(stderr, "All files found\n"); free_sub(sub); return 0; } if ((cmd.action != ACTION_MIX) && (find_volumes(par, m) < m)) { fprintf(stderr, "\nToo many missing files:\n"); for (p = par->files; p; p = p->next) { if (!p->match && USE_FILE(p)) fprintf(stderr, " %s\n", basename(p->filename)); } free_sub(sub); return -1; } if (cmd.action != ACTION_CHECK) { fprintf(stderr, "\nRestoring:\n"); m = restore_files(par->files, par->volumes, sub); free_sub(sub); return m; } fprintf(stderr, "\nRestorable:\n"); for (p = par->files; p; p = p->next) { if ((!p->match) && USE_FILE(p)) fprintf(stderr, " %-40s - can be restored\n", basename(do_sub(p->filename, sub))); } free_sub(sub); return 1; } par-cmdline/checkpar.h0100644000076500007650000000071207366423412014570 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Check PAR files and restore missing files. \*/ #ifndef CHECKPAR_H #define CHECKPAR_H #include "types.h" #include "par.h" int restore_file(pxx_t *pxx); int check_par(par_t *par); #endif /* CHECKPAR_H */ par-cmdline/md5.c0100644000076500007650000003133107370123501013460 0ustar willemwillem/* md5.c - Functions to compute MD5 message digest of files or memory blocks according to the definition of MD5 in RFC 1321 from April 1992. Copyright (C) 1995, 1996 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. 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 2, 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Ulrich Drepper , 1995. */ /* Some changes: * - fix really long streams (64-bit) * - different endianess handling * Willem Monsuwe , 2001 */ #include #include #include #include #include "md5.h" /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (RFC 1321, 3.1: Step 1) */ static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; /* Initialize structure containing state of computation. (RFC 1321, 3.3: Step 3) */ void md5_init_ctx (ctx) struct md5_ctx *ctx; { ctx->A = 0x67452301; ctx->B = 0xefcdab89; ctx->C = 0x98badcfe; ctx->D = 0x10325476; ctx->total[0] = ctx->total[1] = 0; ctx->buflen = 0; } /* Put result from CTX in first 16 bytes following RESBUF. The result must be in little endian byte order. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * md5_read_ctx (ctx, resbuf) const struct md5_ctx *ctx; void *resbuf; { u8 *rb = resbuf; u32 v; v = ctx->A; rb[ 0] = v & 0xFF; v >>= 8; rb[ 1] = v & 0xFF; v >>= 8; rb[ 2] = v & 0xFF; v >>= 8; rb[ 3] = v & 0xFF; v = ctx->B; rb[ 4] = v & 0xFF; v >>= 8; rb[ 5] = v & 0xFF; v >>= 8; rb[ 6] = v & 0xFF; v >>= 8; rb[ 7] = v & 0xFF; v = ctx->C; rb[ 8] = v & 0xFF; v >>= 8; rb[ 9] = v & 0xFF; v >>= 8; rb[10] = v & 0xFF; v >>= 8; rb[11] = v & 0xFF; v = ctx->D; rb[12] = v & 0xFF; v >>= 8; rb[13] = v & 0xFF; v >>= 8; rb[14] = v & 0xFF; v >>= 8; rb[15] = v & 0xFF; return resbuf; } /* Process the remaining bytes in the internal buffer and the usual prolog according to the standard and write the result to RESBUF. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * md5_finish_ctx (ctx, resbuf) struct md5_ctx *ctx; void *resbuf; { /* Take yet unprocessed bytes into account. */ u32 bytes = ctx->buflen; u32 v; size_t pad; u8 *bp; /* Now count remaining bytes. */ ctx->total[0] += bytes; if (ctx->total[0] < bytes) ++ctx->total[1]; pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; memcpy (&ctx->buffer[bytes], fillbuf, pad); bp = &ctx->buffer[bytes + pad]; /* Put the 64-bit file length in *bits* at the end of the buffer. */ v = ctx->total[0]; bp[0] = (v << 3) & 0xFF; v >>= 5; bp[1] = v & 0xFF; v >>= 8; bp[2] = v & 0xFF; v >>= 8; bp[3] = v & 0xFF; v = (v >> 8) | (ctx->total[1] << 3); bp[4] = v & 0xFF; v >>= 8; bp[5] = v & 0xff; v >>= 8; bp[6] = v & 0xFF; v >>= 8; bp[7] = v & 0xFF; /* Process last bytes. */ md5_process_block (ctx->buffer, bytes + pad + 8, ctx); return md5_read_ctx (ctx, resbuf); } /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ i64 md5_stream (stream, resblock) FILE *stream; void *resblock; { /* Important: BLOCKSIZE must be a multiple of 64. */ #define BLOCKSIZE 4096 struct md5_ctx ctx; char buffer[BLOCKSIZE + 72]; size_t sum; i64 tot; tot = 0; /* Initialize the computation context. */ md5_init_ctx (&ctx); /* Iterate over full file contents. */ while (1) { /* We read the file in blocks of BLOCKSIZE bytes. One call of the computation function processes the whole buffer so that with the next round of the loop another block can be read. */ size_t n; sum = 0; /* Read block. Take care for partial reads. */ do { n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); sum += n; } while (sum < BLOCKSIZE && n != 0); if (n == 0 && ferror (stream)) return -1; /* If end of file is reached, end the loop. */ if (n == 0) break; /* Process buffer with BLOCKSIZE bytes. Note that BLOCKSIZE % 64 == 0 */ md5_process_block (buffer, BLOCKSIZE, &ctx); tot += (i64)BLOCKSIZE; } /* Add the last bytes if necessary. */ if (sum > 0) md5_process_bytes (buffer, sum, &ctx); tot += (i64)sum; /* Construct result in desired memory. */ md5_finish_ctx (&ctx, resblock); return tot; } /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ void * md5_buffer (buffer, len, resblock) const char *buffer; size_t len; void *resblock; { struct md5_ctx ctx; /* Initialize the computation context. */ md5_init_ctx (&ctx); /* Process whole buffer but last len % 64 bytes. */ md5_process_bytes (buffer, len, &ctx); /* Put result in desired memory area. */ return md5_finish_ctx (&ctx, resblock); } void md5_process_bytes (buffer, len, ctx) const void *buffer; size_t len; struct md5_ctx *ctx; { /* When we already have some bits in our internal buffer concatenate both inputs first. */ if (ctx->buflen != 0) { size_t left_over = ctx->buflen; size_t add = 128 - left_over > len ? len : 128 - left_over; memcpy (&ctx->buffer[left_over], buffer, add); ctx->buflen += add; if (left_over + add > 64) { md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx); /* The regions in the following copy operation cannot overlap. */ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], (left_over + add) & 63); ctx->buflen = (left_over + add) & 63; } buffer = (const char *) buffer + add; len -= add; } /* Process available complete blocks. */ if (len > 64) { md5_process_block (buffer, len & ~63, ctx); buffer = (const char *) buffer + (len & ~63); len &= 63; } /* Move remaining bytes in internal buffer. */ if (len > 0) { memcpy (ctx->buffer, buffer, len); ctx->buflen = len; } } /* These are the four functions used in the four steps of the MD5 algorithm and defined in the RFC 1321. The first function is a little bit optimized (as found in Colin Plumbs public domain implementation). */ /* #define FF(b, c, d) ((b & c) | (~b & d)) */ #define FF(b, c, d) (d ^ (b & (c ^ d))) #define FG(b, c, d) FF (d, b, c) #define FH(b, c, d) (b ^ c ^ d) #define FI(b, c, d) (c ^ (b | ~d)) /* Process LEN bytes of BUFFER, accumulating context into CTX. It is assumed that LEN % 64 == 0. */ void md5_process_block (buffer, len, ctx) const void *buffer; size_t len; struct md5_ctx *ctx; { u32 correct_words[16]; const u8 *words = buffer; const u8 *endp = words + len; u32 A = ctx->A; u32 B = ctx->B; u32 C = ctx->C; u32 D = ctx->D; /* First increment the byte count. RFC 1321 specifies the possible length of the file up to 2^64 bits. Here we only compute the number of bytes. Do a double word increment. */ ctx->total[0] += len; if (ctx->total[0] < len) ++ctx->total[1]; /* Process all bytes in the buffer with 64 bytes in each round of the loop. */ while (words < endp) { u32 *cwp = correct_words; u32 A_save = A; u32 B_save = B; u32 C_save = C; u32 D_save = D; /* First round: using the given function, the context and a constant the next context is computed. Because the algorithms processing unit is a 32-bit word and it is determined to work on words in little endian byte order we perhaps have to change the byte order before the computation. To reduce the work for the next steps we store the swapped words in the array CORRECT_WORDS. */ #define OP(a, b, c, d, s, T) \ do \ { \ *cwp = *words++; \ *cwp |= *words++ << 8; \ *cwp |= *words++ << 16; \ *cwp |= *words++ << 24; \ a += FF (b, c, d) + *cwp++ + T; \ CYCLIC (a, s); \ a += b; \ } \ while (0) /* It is unfortunate that C does not provide an operator for cyclic rotation. Hope the C compiler is smart enough. */ #define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) /* Before we start, one word to the strange constants. They are defined in RFC 1321 as T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 */ /* Round 1. */ OP (A, B, C, D, 7, 0xd76aa478); OP (D, A, B, C, 12, 0xe8c7b756); OP (C, D, A, B, 17, 0x242070db); OP (B, C, D, A, 22, 0xc1bdceee); OP (A, B, C, D, 7, 0xf57c0faf); OP (D, A, B, C, 12, 0x4787c62a); OP (C, D, A, B, 17, 0xa8304613); OP (B, C, D, A, 22, 0xfd469501); OP (A, B, C, D, 7, 0x698098d8); OP (D, A, B, C, 12, 0x8b44f7af); OP (C, D, A, B, 17, 0xffff5bb1); OP (B, C, D, A, 22, 0x895cd7be); OP (A, B, C, D, 7, 0x6b901122); OP (D, A, B, C, 12, 0xfd987193); OP (C, D, A, B, 17, 0xa679438e); OP (B, C, D, A, 22, 0x49b40821); /* For the second to fourth round we have the possibly swapped words in CORRECT_WORDS. Redefine the macro to take an additional first argument specifying the function to use. */ #undef OP #define OP(f, a, b, c, d, k, s, T) \ do \ { \ a += f (b, c, d) + correct_words[k] + T; \ CYCLIC (a, s); \ a += b; \ } \ while (0) /* Round 2. */ OP (FG, A, B, C, D, 1, 5, 0xf61e2562); OP (FG, D, A, B, C, 6, 9, 0xc040b340); OP (FG, C, D, A, B, 11, 14, 0x265e5a51); OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); OP (FG, A, B, C, D, 5, 5, 0xd62f105d); OP (FG, D, A, B, C, 10, 9, 0x02441453); OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); OP (FG, D, A, B, C, 14, 9, 0xc33707d6); OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); OP (FG, B, C, D, A, 8, 20, 0x455a14ed); OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); OP (FG, C, D, A, B, 7, 14, 0x676f02d9); OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); /* Round 3. */ OP (FH, A, B, C, D, 5, 4, 0xfffa3942); OP (FH, D, A, B, C, 8, 11, 0x8771f681); OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); OP (FH, B, C, D, A, 14, 23, 0xfde5380c); OP (FH, A, B, C, D, 1, 4, 0xa4beea44); OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); OP (FH, B, C, D, A, 6, 23, 0x04881d05); OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); /* Round 4. */ OP (FI, A, B, C, D, 0, 6, 0xf4292244); OP (FI, D, A, B, C, 7, 10, 0x432aff97); OP (FI, C, D, A, B, 14, 15, 0xab9423a7); OP (FI, B, C, D, A, 5, 21, 0xfc93a039); OP (FI, A, B, C, D, 12, 6, 0x655b59c3); OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); OP (FI, C, D, A, B, 10, 15, 0xffeff47d); OP (FI, B, C, D, A, 1, 21, 0x85845dd1); OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); OP (FI, C, D, A, B, 6, 15, 0xa3014314); OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); OP (FI, A, B, C, D, 4, 6, 0xf7537e82); OP (FI, D, A, B, C, 11, 10, 0xbd3af235); OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); OP (FI, B, C, D, A, 9, 21, 0xeb86d391); /* Add the starting values of the context. */ A += A_save; B += B_save; C += C_save; D += D_save; } /* Put checksum in context given as argument. */ ctx->A = A; ctx->B = B; ctx->C = C; ctx->D = D; } par-cmdline/interface.c0100644000076500007650000002116207375541222014745 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| This file holds an interface abstraction, |*| to be called by different user interfaces. \*/ #include "interface.h" #include "par.h" #include "rwpar.h" #include "backend.h" #include "util.h" #include "fileops.h" #include #include static pfile_t *volumes = 0, *files = 0; static sub_t *sub = 0; extern hfile_t *hfile; /*\ Get the current flags |*| Returns: Current flags \*/ int par_flags(void) { int ret = 0; if (cmd.move) ret |= PARFLAG_MOVE; if (cmd.usecase) ret |= PARFLAG_CASE; if (cmd.ctrl) ret |= PARFLAG_CTRL; if (cmd.keep) ret |= PARFLAG_KEEP; return ret; } /*\ Set some flags |*| Returns: Current flags \*/ int par_setflags(int flags) { if (flags & PARFLAG_MOVE) cmd.move = 1; if (flags & PARFLAG_CASE) cmd.usecase = 1; if (flags & PARFLAG_CTRL) cmd.ctrl = 1; if (flags & PARFLAG_KEEP) cmd.keep = 1; return par_flags(); } /*\ Set some flags |*| Returns: Current flags \*/ int par_unsetflags(int flags) { if (flags & PARFLAG_MOVE) cmd.move = 0; if (flags & PARFLAG_CASE) cmd.usecase = 0; if (flags & PARFLAG_CTRL) cmd.ctrl = 0; if (flags & PARFLAG_KEEP) cmd.keep = 0; return par_flags(); } /*\ Add a PARfile to the current parlist. |*| filename: Name of file to load \*/ int par_load(u16 *filename) { pfile_t *p, **pp; par_t *par; if (!filename) return PAR_ERR_INVALID; par = read_par_header(filename, 1, 0, 0); if (!par) return PAR_ERR_ERRNO; for (p = volumes; p; p = p->next) { if (!unicode_cmp(p->filename, par->filename)) { free_par(par); return PAR_ERR_ALREADY_LOADED; } } CNEW(p, 1); p->match = find_file_name(par->filename, 0); p->vol_number = par->vol_number; p->file_size = par->data_size; if (par->files) p->fnrs = file_numbers(&files, &par->files); p->f = par->f; p->filename = unicode_copy(par->filename); par->f = 0; free_par(par); /*\ Insert in alphabetically correct place \*/ for (pp = &volumes; *pp; pp = &((*pp)->next)) if (unicode_gt((*pp)->filename, filename)) break; p->next = *pp; *pp = p; return PAR_OK; } /*\ Search for additional PARfiles matching the current ones |*| partial: Load files that only match partially. \*/ int par_search(int partial) { find_par_files(&volumes, &files, partial); return PAR_OK; } /*\ Remove a PARfile from the current parlist. |*| entry: Name of file to remove \*/ int par_unload(u16 *entry) { pfile_t *p, **pp; for (pp = &volumes; *pp; pp = &((*pp)->next)) { if ((*pp)->filename == entry) { p = *pp; *pp = p->next; if (p->f) file_close(p->f); if (p->fnrs) free(p->fnrs); free(p->filename); free(p); return PAR_OK; } } return PAR_ERR_EXIST; } /*\ List the current PARfiles |*| Returns: Array of filenames, NULL-terminated. |*| Notes: Caller should free() returned array. \*/ u16 ** par_parlist(void) { u16 **ret; pfile_t *p; int n; for (n = 0, p = volumes; p; p = p->next) n++; CNEW(ret, n + 1); for (n = 0, p = volumes; p; p = p->next, n++) ret[n] = p->filename; ret[n] = 0; return ret; } /*\ List the current filelist |*| Returns: Array of filenames, NULL-terminated. |*| Notes: Caller should free() returned array. \*/ u16 ** par_filelist(void) { u16 **ret; pfile_t *p; int n; for (n = 0, p = files; p; p = p->next) n++; CNEW(ret, n + 1); for (n = 0, p = files; p; p = p->next, n++) ret[n] = p->filename; ret[n] = 0; return ret; } /*\ Check the MD5 sum of a file |*| entry: file to check \*/ int par_check(u16 *entry) { pfile_t *p; for (p = files; p; p = p->next) { if (p->filename == entry) { if (find_file(p, 0)) return PAR_OK; return PAR_ERR_CORRUPT; } } return PAR_ERR_EXIST; } /*\ Find a file by its MD5 sum |*| entry: the file to find |*| Returns: matching filename |*| Notes: Returned filename should NOT be free()d. \*/ u16 * par_find(u16 *entry) { pfile_t *p; for (p = files; p; p = p->next) { if (p->filename == entry) { if (!find_file(p, 0)) return 0; return p->match->filename; } } return 0; } /*\ Fix incorrect filenames |*| entry(optional): only fix this entry \*/ int par_fixname(u16 *entry) { int err = PAR_OK; pfile_t *p; u16 *path; for (p = files; p; p = p->next) { if (entry && (p->filename != entry)) continue; if (!find_file(p, 0)) { err = PAR_ERR_NOT_FOUND; continue; } path = do_sub(p->filename, sub); if (!unicode_cmp(path, p->match->filename)) continue; if (rename_away(p->match->filename, path)) err = PAR_ERR_ERRNO; } return err; } /*\ Get status word |*| entry: file to get status of |*| Returns: status word for entry. \*/ i64 par_getstatus(u16 *entry) { pfile_t *p; for (p = files; p; p = p->next) if (p->filename == entry) return p->status; return 0; } /*\ Set status word |*| entry: file to set status for \*/ int par_setstatus(u16 *entry, i64 status) { pfile_t *p; for (p = files; p; p = p->next) { if (p->filename == entry) { p->status = status; return PAR_OK; } } return PAR_ERR_EXIST; } /*\ Recover missing files |*| entry(optional): only recover this entry \*/ int par_recover(u16 *entry) { if (entry) return PAR_ERR_IMPL; if (restore_files(files, volumes, sub) < 0) return PAR_ERR_FAILED; return PAR_OK; } /*\ Add a file to the current filelist |*| filename: file to add \*/ int par_addfile(u16 *filename) { pfile_t *p, **pp; hfile_t *file; if (!filename) return PAR_ERR_INVALID; file = find_file_name(filename, 0); if (!file) return PAR_ERR_NOT_FOUND; if (!hash_file(file, HASH)) return PAR_ERR_ERRNO; for (p = files; p; p = p->next) { if (!unicode_cmp(p->filename, filename)) { if (CMP_MD5(p->hash, file->hash)) return PAR_ERR_ALREADY_LOADED; else return PAR_ERR_CLASH; } } /*\ Create new entry \*/ CNEW(p, 1); p->filename = file->filename; p->match = file; p->file_size = file->file_size; COPY(p->hash, file->hash, sizeof(md5)); COPY(p->hash_16k, file->hash_16k, sizeof(md5)); p->status |= 0x01; /*\ Insert in alphabetically correct place \*/ for (pp = &files; *pp; pp = &((*pp)->next)) if (unicode_gt((*pp)->filename, p->filename)) break; p->next = *pp; *pp = p; return PAR_OK; } /*\ Remove a file from the current filelist |*| entry: file to remove \*/ int par_removefile(u16 *entry) { return PAR_ERR_IMPL; } /*\ Add new PARfiles to the current parlist |*| number: the highest volume number to create \*/ int par_addpars(u16 *entry, int number) { int i, err = PAR_OK; pfile_t *p, **pp; hfile_t *h; if (number < 1) return PAR_ERR_INVALID; for (p = volumes; p; p = p->next) if (p->filename == entry) break; if (!p) return PAR_ERR_INVALID; for (i = 1; i <= number; i++) { h = find_volume(entry, i); if (!h) { err = PAR_ERR_ERRNO; continue; } for (p = volumes; p; p = p->next) if (!unicode_cmp(p->filename, h->filename)) break; if (p) continue; CNEW(p, 1); p->match = h; p->vol_number = i; p->filename = unicode_copy(h->filename); /*\ Insert in alphabetically correct place \*/ for (pp = &volumes; *pp; pp = &((*pp)->next)) if (unicode_gt((*pp)->filename, p->filename)) break; p->next = *pp; *pp = p; } return err; } /*\ Create PARfiles from the current filelist |*| entry(optional): only create this entry \*/ int par_create(u16 *entry) { pfile_t *p; if (entry) return PAR_ERR_IMPL; for (p = volumes; p; p = p->next) { if (p->f) { file_close(p->f); p->f = 0; } if (!p->vol_number) { par_t *par; par = create_par_header(p->filename, 0); par->files = files; write_par_header(par); par->files = 0; free_par(par); } if (!p->fnrs) p->fnrs = file_numbers(&files, &files); } if (restore_files(files, volumes, sub) < 0) return PAR_ERR_FAILED; return PAR_OK; } /*\ List the current directories |*| Returns: Array of filenames, NULL-terminated. |*| Notes: Calles should free() returned array. \*/ u16 ** par_dirlist(void) { u16 **ret; hfile_t *p; int n; for (n = 0, p = hfile; p; p = p->next) n++; CNEW(ret, n + 1); for (n = 0, p = hfile; p; p = p->next, n++) ret[n] = p->filename; ret[n] = 0; return ret; } /*\ |*| Set the smart renaming pattern |*| Note: Optional arguments are to specify pattern, |*| otherwise the pattern is taken from the current list. \*/ int par_setsmart(u16 *from, u16 *to) { if (!from) { if (!to) { free_sub(sub); sub = find_best_sub(files, 2); return PAR_OK; } else { return PAR_ERR_IMPL; } } else { if (!to) { return PAR_ERR_IMPL; } else { free_sub(sub); sub = make_sub(from, to); return PAR_OK; } } } par-cmdline/interface.h0100644000076500007650000000666607375540045014770 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| This file holds an interface abstraction, |*| to be called by different user interfaces. \*/ #ifndef INTERFACE_H #define INTERFACE_H #include "types.h" /*\ Errorcodes \*/ #define PAR_OK 0 #define PAR_ERR_ERRNO 1 #define PAR_ERR_EXIST 2 #define PAR_ERR_NOT_FOUND 3 #define PAR_ERR_CORRUPT 4 #define PAR_ERR_FAILED 5 #define PAR_ERR_ALREADY_LOADED 6 #define PAR_ERR_IMPL 7 #define PAR_ERR_CLASH 8 #define PAR_ERR_INVALID 9 #define PARFLAG_MOVE 0x1 #define PARFLAG_CASE 0x2 #define PARFLAG_CTRL 0x4 #define PARFLAG_KEEP 0x8 /*\ Get the current flags |*| Returns: Current flags \*/ int par_flags(void); /*\ Set some flags |*| Returns: Current flags \*/ int par_setflags(int flags); /*\ Set some flags |*| Returns: Current flags \*/ int par_unsetflags(int flags); /*\ Add a PARfile to the current parlist. |*| filename: Name of file to load \*/ int par_load(u16 *filename); /*\ Search for additional PARfiles matching the current ones |*| partial: Load files that only match partially. \*/ int par_search(int partial); /*\ Remove a PARfile from the current parlist. |*| entry: Name of file to remove \*/ int par_unload(u16 *entry); /*\ List the current PARfiles |*| Returns: Array of filenames, NULL-terminated. |*| Notes: Caller should free() returned array, but not the filenames. |*| These pointers should be used as 'entry' arguments. \*/ u16 ** par_parlist(void); /*\ List the current filelist |*| Returns: Array of filenames, NULL-terminated. |*| Notes: Caller should free() returned array, but not the filenames. |*| These pointers should be used as 'entry' arguments. \*/ u16 ** par_filelist(void); /*\ Check the MD5 sum of a file |*| entry: file to check \*/ int par_check(u16 *entry); /*\ Find a file by its MD5 sum |*| entry: the file to find |*| Returns: matching filename |*| Notes: Returned filename should NOT be free()d. \*/ u16 * par_find(u16 *entry); /*\ Fix incorrect filenames |*| entry(optional): only fix this entry \*/ int par_fixname(u16 *entry); /*\ Get status word |*| entry: file to get status of |*| Returns: status word for entry. \*/ i64 par_getstatus(u16 *entry); /*\ Set status word |*| entry: file to set status for \*/ int par_setstatus(u16 *entry, i64 status); /*\ Recover missing files |*| entry(optional): only recover this entry \*/ int par_recover(u16 *entry); /*\ Add a file to the current filelist |*| filename: file to add \*/ int par_addfile(u16 *filename); /*\ Remove a file from the current filelist |*| entry: file to remove \*/ int par_removefile(u16 *entry); /*\ Add new PARfiles to the current parlist |*| number: the highest volume number to create \*/ int par_addpars(u16 *entry, int number); /*\ Create PARfiles from the current filelist |*| entry(optional): only create this entry \*/ int par_create(u16 *entry); /*\ List the current directories |*| Returns: Array of filenames, NULL-terminated. |*| Notes: Calles should free() returned array. \*/ u16 ** par_dirlist(void); /*\ |*| Set the smart renaming pattern |*| Note: Optional arguments are to specify pattern, |*| otherwise the pattern is taken from the current list. \*/ int par_setsmart(u16 *from, u16 *to); #endif /*\ INTERFACE_H \*/ par-cmdline/main.c0100644000076500007650000001303707375535254013743 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| In short: Files are XORed with each other and the result is stored. |*| From this, one missing file can be recreated. \*/ #include #include #include #include "fileops.h" #include "rwpar.h" #include "backend.h" #include "checkpar.h" #include "makepar.h" #include "interface.h" struct cmdline cmd; /*\ |*| Print usage \*/ int usage(void) { printf( "Usage:\n" " par c(heck) [options] : Check parity archive\n" " par r(ecover) [options] : Restore missing volumes\n" " par a(dd) [options] [files] : Add files to parity archive\n" " Advanced:\n" " par m(ix) [options] : Try to restore from all parity files at once\n" " par i(nteractive) [] : Interactive mode (very bare-bones)\n" "\n" "Options: (Can be turned off with '+')\n" " -m : Move existing files out of the way\n" " -f : Fix faulty filenames\n" " -p: Number of files per parity volume\n" " or -n: Number of parity volumes to create\n" " -d : Search for duplicate files\n" " -k : Keep broken files\n" " -s : Be smart if filenames are consistently different.\n" " +i : Do not add following files to parity volumes\n" " +c : Do not create parity volumes\n" " +C : Ignore case in filename comparisons\n" " +H : Do not check control hashes\n" " -v,+v: Increase or decrease verbosity\n" " -h,-?: Display this help\n" " -- : Always treat following arguments as files\n" "\n" ); return 0; } /*\ Sanity check \*/ static int check_sizes(void) { int fail = 0; if (sizeof(u8) != 1) { fprintf(stderr, "u8 isn't 8 bits wide.\n"); fail++; } if (sizeof(u16) != 2) { fprintf(stderr, "u16 isn't 16 bits wide.\n"); fail++; } if (sizeof(u32) != 4) { fprintf(stderr, "u32 isn't 32 bits wide.\n"); fail++; } if (sizeof(i64) != 8) { fprintf(stderr, "u64 isn't 64 bits wide.\n"); fail++; } return fail; } /*\ In ui_text.h \*/ void ui_text(void); /*\ |*| Main loop. Simple stuff. \*/ int main(int argc, char *argv[]) { par_t *par = 0; int fail = 0; char *p; if (check_sizes()) return -1; /*\ Some defaults \*/ memset(&cmd, 0, sizeof(cmd)); cmd.volumes = 10; cmd.pervol = 1; cmd.pxx = 1; cmd.ctrl = 1; cmd.add = 1; cmd.usecase = 1; if (argc == 1) return usage(); for (; argc > 1; argc--, argv++) { if (((argv[1][0] == '-') || (argv[1][0] == '+')) && argv[1][1] && !cmd.dash) { for (p = argv[1]; *p; p++) switch (*p) { case '-': if (p[1] == '-') cmd.dash = 1; else cmd.plus = 1; break; case '+': cmd.plus = 0; break; case 'm': cmd.move = cmd.plus; break; case 'i': cmd.add = cmd.plus; break; case 'f': cmd.fix = cmd.plus; break; case 'c': cmd.pxx = cmd.plus; break; case 'd': cmd.dupl = cmd.plus; break; case 'v': if (cmd.plus) cmd.loglevel++; else cmd.loglevel--; break; case 'p': case 'n': cmd.pervol = (*p == 'p'); while (isspace(*++p)) ; if (!*p) { argv++; argc--; p = argv[1]; } if (!isdigit(*p)) { fprintf(stderr, "Value expected!\n"); } else { cmd.volumes = 0; do { cmd.volumes *= 10; cmd.volumes += *p - '0'; } while (isdigit(*++p)); p--; } break; case 'H': cmd.ctrl = cmd.plus; break; case 'C': cmd.usecase = cmd.plus; break; case 'k': cmd.keep = cmd.plus; break; case 's': cmd.smart = cmd.plus; break; case '?': case 'h': return usage(); default: fprintf(stderr, "Unknown switch: '%c'\n", *p); break; } continue; } if (!cmd.action) { switch (argv[1][0]) { case 'c': case 'C': cmd.action = ACTION_CHECK; break; case 'm': case 'M': cmd.action = ACTION_MIX; break; case 'r': case 'R': cmd.action = ACTION_RESTORE; break; case 'a': case 'A': cmd.action = ACTION_ADD; break; case 'i': case 'I': cmd.action = ACTION_TEXT_UI; break; default: fprintf(stderr, "Unknown command: '%s'\n", argv[1]); break; } continue; } switch (cmd.action) { default: cmd.action = ACTION_CHECK; /*\ FALLTHROUGH \*/ case ACTION_CHECK: case ACTION_RESTORE: fprintf(stderr, "Checking %s\n", argv[1]); par = read_par_header(unist(argv[1]), 0, 0, 0); if (!par) { fail = 2; continue; } if (check_par(par) < 0) fail = 1; free_par(par); par = 0; break; case ACTION_ADD: fprintf(stderr, "Adding to %s\n", argv[1]); par = read_par_header(unist(argv[1]), 1, 0, 0); if (!par) return 2; cmd.action = ACTION_ADDING; break; case ACTION_ADDING: par_add_file(par, find_file_name(unist(argv[1]), 1)); break; case ACTION_MIX: fprintf(stderr, "Unknown argument: '%s'\n", argv[1]); break; case ACTION_TEXT_UI: par_load(unist(argv[1])); break; } } if (cmd.action == ACTION_TEXT_UI) { ui_text(); return 0; } if (cmd.action == ACTION_MIX) { par = find_all_par_files(); if (par) { fprintf(stderr, "\nChecking:\n"); if (check_par(par) < 0) fail = 1; free_par(par); par = 0; } else { fail = 2; } } if (par) { if (cmd.pxx && !par_make_pxx(par)) fail |= 1; if (!par->vol_number && !write_par_header(par)) fail |= 1; free_par(par); } return fail; } par-cmdline/makepar.c0100644000076500007650000000560407375534534014440 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Create PAR files. \*/ #include #include #include #include #include #include "util.h" #include "backend.h" #include "makepar.h" #include "rwpar.h" #include "fileops.h" /*\ |*| Add a data file to a PAR file \*/ int par_add_file(par_t *par, hfile_t *file) { pfile_t *p, **pp; if (!file) return 0; if (!hash_file(file, HASH)) { fprintf(stderr, " %-40s - ERROR\n", basename(file->filename)); return 0; } /*\ Check if the file exists \*/ for (p = par->files; p; p = p->next) { switch (unicode_cmp(p->filename, file->filename)) { case 0: if (CMP_MD5(p->hash, file->hash)) { fprintf(stderr, " %-40s - EXISTS\n", basename(file->filename)); } else { fprintf(stderr, " %-40s - NAME CLASH\n", basename(file->filename)); } return 0; case 1: if (CMP_MD5(p->hash, file->hash)) { fprintf(stderr, " %-40s - EXISTS\n", basename(file->filename)); return 0; } break; } } /*\ Create new entry \*/ CNEW(p, 1); p->filename = file->filename; p->match = file; p->file_size = file->file_size; COPY(p->hash, file->hash, sizeof(md5)); COPY(p->hash_16k, file->hash_16k, sizeof(md5)); if (cmd.add) p->status |= 0x01; /*\ Insert in alphabetically correct place \*/ for (pp = &par->files; *pp; pp = &((*pp)->next)) if (unicode_gt((*pp)->filename, p->filename)) break; p->next = *pp; *pp = p; fprintf(stderr, " %-40s - OK\n", basename(file->filename)); return 1; } /*\ |*| Create the PAR volumes from the description in the PAR archive \*/ int par_make_pxx(par_t *par) { pfile_t *p, *v; int M, i; if (!IS_PAR(*par)) return 0; if (par->vol_number) { CNEW(v, 1); v->match = find_file_name(par->filename, 0); if (!v->match) v->match = find_volume(par->filename, par->vol_number); v->vol_number = par->vol_number; if (v->match) v->filename = v->match->filename; par->volumes = v; } else { if (cmd.volumes <= 0) return 0; M = cmd.volumes; if (cmd.pervol) { for (M = 0, p = par->files; p; p = p->next) if (USE_FILE(p)) M++; M = ((M - 1) / cmd.volumes) + 1; } /*\ Create volume file entries \*/ for (i = 1; i <= M; i++) { CNEW(v, 1); v->match = find_volume(par->filename, i); v->vol_number = i; if (v->match) v->filename = v->match->filename; v->next = par->volumes; par->volumes = v; } } fprintf(stderr, "\n\nCreating PAR volumes:\n"); for (p = par->files; p; p = p->next) if (USE_FILE(p)) find_file(p, 1); for (v = par->volumes; v; v = v->next) v->fnrs = file_numbers(&par->files, &par->files); if (restore_files(par->files, par->volumes, 0) < 0) return 0; return 1; } par-cmdline/makepar.h0100644000076500007650000000066507366423412014437 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Create PAR and PXX files. \*/ #ifndef MAKEPAR_H #define MAKEPAR_H #include "par.h" int par_add_file(par_t *par, hfile_t *file); int par_make_pxx(par_t *par); #endif /* MAKEPAR_H */ par-cmdline/md5.h0100644000076500007650000000703207366423412013477 0ustar willemwillem/* md5.h - Declaration of functions and data types used for MD5 sum computing library functions. Copyright (C) 1995, 1996, 1999 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. 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 2, 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _MD5_H #define _MD5_H 1 #include #include "types.h" #undef __P #if defined (__STDC__) && __STDC__ #define __P(x) x #else #define __P(x) () #endif /* Structure to save state of computation between the single steps. */ struct md5_ctx { u32 A; u32 B; u32 C; u32 D; u32 total[2]; u32 buflen; char buffer[128]; }; /* * The following three functions are build up the low level used in * the functions `md5_stream' and `md5_buffer'. */ /* Initialize structure containing state of computation. (RFC 1321, 3.3: Step 3) */ extern void md5_init_ctx __P ((struct md5_ctx *ctx)); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is necessary that LEN is a multiple of 64!!! */ extern void md5_process_block __P ((const void *buffer, size_t len, struct md5_ctx *ctx)); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is NOT required that LEN is a multiple of 64. */ extern void md5_process_bytes __P ((const void *buffer, size_t len, struct md5_ctx *ctx)); /* Process the remaining bytes in the buffer and put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF be correctly aligned for a 32 bits value. */ extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf)); /* Put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf)); /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ extern i64 md5_stream __P ((FILE *stream, void *resblock)); /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock)); #endif par-cmdline/rwpar.c0100644000076500007650000003642607375543171014156 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Read and write PAR files \*/ #include #include #include #include #include #include "util.h" #include "rwpar.h" #include "fileops.h" #include "rs.h" #include "readoldpar.h" #include "md5.h" #include "backend.h" /*\ Endianless fixing code \*/ static i64 read_i64(void *data) { int i; i64 r = 0; u8 *ptr = data; for (i = sizeof(i64); --i >= 0; ) { r <<= 8; r += (i64)ptr[i]; } return r; } static u32 read_u32(void *data) { int i; u32 r = 0; u8 *ptr = data; for (i = sizeof(u32); --i >= 0; ) { r <<= 8; r += (u32)ptr[i]; } return r; } /*\ Read N bytes of little-endian u16s \*/ static void read_u16s(u16 *str, void *data, i64 n) { u8 *ptr = data; while (--n >= 0) { *str = ptr[0] + (ptr[1] << 8); str++; ptr += 2; } } static void write_i64(i64 v, void *data) { size_t i; u8 *ptr = data; for (i = 0; i < sizeof(i64); i++) { ptr[i] = v & 0xFF; v >>= 8; } } static void write_u32(u32 v, void *data) { size_t i; u8 *ptr = data; for (i = 0; i < sizeof(u32); i++) { ptr[i] = v & 0xFF; v >>= 8; } } /*\ Write N bytes of little-endian u16s \*/ static void write_u16s(u16 *str, void *data, i64 n) { u8 *ptr = data; while (--n >= 0) { ptr[0] = (*str) & 0xff; ptr[1] = ((*str) >> 8) & 0xff; str++; ptr += 2; } } /*\ Change endianness to host byte order |*| NB: This is a fix in place. Don't call this twice! \*/ static void par_endian_read(par_t *par) { par->version = read_u32(&par->version); par->client = read_u32(&par->client); par->vol_number = read_i64(&par->vol_number); par->num_files = read_i64(&par->num_files); par->file_list = read_i64(&par->file_list); par->file_list_size = read_i64(&par->file_list_size); par->data = read_i64(&par->data); par->data_size = read_i64(&par->data_size); } static void par_endian_write(par_t *par, void *data) { par_t *p = (par_t *)data; memcpy(p, par, PAR_FIX_HEAD_SIZE); write_u32(par->version, &p->version); write_u32(par->client, &p->client); write_i64(par->vol_number, &p->vol_number); write_i64(par->num_files, &p->num_files); write_i64(par->file_list, &p->file_list); write_i64(par->file_list_size, &p->file_list_size); write_i64(par->data, &p->data); write_i64(par->data_size, &p->data_size); } static u16 uni_empty[] = { 0 }; static i64 uni_sizeof(u16 *str) { i64 l; for (l = 0; str[l]; l++) ; return (2 * l); } /*\ |*| Return a pointer just past the last occurrence of '/' in a unicode string |*| (somewhat like strrchr) \*/ static u16 * uni_strip(u16 *str) { u16 *ret; for (ret = str; *str; str++) if (*str == DIR_SEP) ret = str + 1; return ret; } /*\ |*| Debugging output functions \*/ static void dump_file(pfile_t *file) { fprintf(stderr, " status: 0x%llx\n" " file size: %lld\n" " hash: %s\n", file->status, file->file_size, stmd5(file->hash)); fprintf(stderr, " 16k hash: %s\n", stmd5(file->hash_16k)); fprintf(stderr, " filename: %s\n", stuni(file->filename)); } void dump_par(par_t *par) { pfile_t *p; fprintf(stderr, "PAR file dump:\n" " filename: %s\n" " version: 0x%04x\n" " client: 0x%04x\n" " control hash: %s\n", stuni(par->filename), par->version, par->client, stmd5(par->control_hash)); fprintf(stderr, " set hash: %s\n", stmd5(par->set_hash)); fprintf(stderr, " volume number: %lld\n" " number of files: %lld\n" " file list: 0x%llx\n" " file list size: 0x%llx\n" " data: 0x%llx\n" " data size: 0x%llx\n", par->vol_number, par->num_files, par->file_list, par->file_list_size, par->data, par->data_size); if (!par->vol_number) fprintf(stderr, " comment: %s\n", stuni(par->comment)); fprintf(stderr, "\nFiles:\n\n"); for (p = par->files; p; p = p->next) dump_file(p); } /*\ |*| Read in a PAR file entry to a file struct \*/ static i64 read_pfile(pfile_t *file, u8 *ptr, u16 *path, i64 pl) { i64 i, l; pfile_entr_t *pf; pf = ((pfile_entr_t *)ptr); i = read_i64(&pf->size); file->status = read_i64(&pf->status); file->file_size = read_i64(&pf->file_size); COPY(file->hash, pf->hash, sizeof(md5)); COPY(file->hash_16k, pf->hash_16k, sizeof(md5)); l = (i - FILE_ENTRY_FIX_SIZE) / 2; NEW(file->filename, pl + l + 1); COPY(file->filename, path, pl); read_u16s(file->filename + pl, &pf->filename, l); file->filename[l + pl] = 0; return i; } /*\ |*| Make a list of pointers into a list of file entries \*/ static pfile_t * read_pfiles(file_t f, i64 size, u16 *path) { pfile_t *files = 0, **fptr = &files; u8 *buf; i64 i, pl; for (pl = i = 0; path[i]; i++) if (path[i] == DIR_SEP) pl = i + 1; NEW(buf, size); size = file_read(f, buf, size); /*\ The list size is at the start of the block \*/ i = 0; /*\ Loop over the entries; the size of an entry is at the start \*/ while (i < size) { CNEW(*fptr, 1); i += read_pfile(*fptr, buf + i, path, pl); fptr = &((*fptr)->next); } free(buf); return files; } /*\ |*| Create a new PAR file struct \*/ par_t * create_par_header(u16 *file, i64 vol) { par_t *par; CNEW(par, 1); par->magic = PAR_MAGIC; par->version = 0x00010000; par->client = 0x02000900; par->vol_number = vol; par->filename = unicode_copy(file); par->comment = uni_empty; par->control_hash_offset = 0x20; return par; } /*\ |*| Read in a PAR file, and return it into a newly allocated struct |*| (to be freed with free_par()) \*/ par_t * read_par_header(u16 *file, int create, i64 vol, int silent) { par_t par, *r; char *path; memset(&par, 0, sizeof(par)); hash_directory(stuni(file)); path = complete_path(stuni(file)); par.f = file_open(file, 0); /*\ Read in the first part of the struct, it fits directly on top \*/ if (file_read(par.f, &par, PAR_FIX_HEAD_SIZE) < PAR_FIX_HEAD_SIZE) { if (!create || (errno != ENOENT)) { if (!silent) perror("Error reading PAR file"); file_close(par.f); return 0; } if (!vol) { /*\ Guess volume number from last digits \*/ u16 *p; for (p = file; *p; p++) ; while ((--p >= file) && (*p >= '0') && (*p <= '9')) ; while (*++p) vol = vol * 10 + (*p - '0'); } return create_par_header(file, vol); } /*\ Is it the right file type ? \*/ if (!IS_PAR(par)) { if (is_old_par(&par)) if (file_seek(par.f, 0) >= 0) return read_old_par(par.f, file, silent); if (!silent) fprintf(stderr, "%s: Not a PAR file\n", basename(file)); file_close(par.f); return 0; } par_endian_read(&par); par.control_hash_offset = 0x20; par.filename = file; if (!silent && !par_control_check(&par)) { file_close(par.f); return 0; } file_seek(par.f, par.file_list); par.filename = make_uni_str(path); /*\ Read in the filelist. \*/ par.files = read_pfiles(par.f, par.file_list_size, par.filename); file_seek(par.f, par.data); if (par.vol_number == 0) { CNEW(par.comment, (par.data_size / 2) + 1); file_read(par.f, par.comment, par.data_size); file_close(par.f); par.f = 0; } par.volumes = 0; NEW(r, 1); COPY(r, &par, 1); if (cmd.loglevel > 1) dump_par(r); return r; } void free_file_list(pfile_t *list) { pfile_t *next; while (list) { if (list->f) file_close(list->f); if (list->fnrs) free(list->fnrs); next = list->next; free(list); list = next; } } void free_par(par_t *par) { free_file_list(par->files); free_file_list(par->volumes); free(par->filename); if (par->f) file_close(par->f); free(par); } /*\ |*| Write out a PAR file entry from a file struct \*/ static i64 write_pfile(pfile_t *file, pfile_entr_t *pf) { u16 *name; i64 i; name = uni_strip(file->filename); i = uni_sizeof(name); write_i64(FILE_ENTRY_FIX_SIZE + i, &pf->size); write_i64(file->status, &pf->status); write_i64(file->file_size, &pf->file_size); COPY(pf->hash, file->hash, sizeof(md5)); COPY(pf->hash_16k, file->hash_16k, sizeof(md5)); write_u16s(name, &pf->filename, i / 2); return FILE_ENTRY_FIX_SIZE + i; } /*\ |*| Write a list of file entries to a file \*/ static i64 write_file_entries(file_t f, pfile_t *files) { i64 tot, t, m; pfile_t *p; pfile_entr_t *pfe; tot = m = 0; for (p = files; p; p = p->next) { t = FILE_ENTRY_FIX_SIZE + uni_sizeof(uni_strip(p->filename)); tot += t; if (m < t) m = t; } pfe = (pfile_entr_t *)malloc(m); if (f) { for (p = files; p; p = p->next) { t = write_pfile(p, pfe); file_write(f, pfe, t); } } free(pfe); return tot; } /*\ |*| Write out a PAR volume header \*/ file_t write_par_header(par_t *par) { file_t f; par_t data; pfile_t *p; int i; md5 *hashes; /*\ Open output file, but check so we don't overwrite anything \*/ if (move_away(par->filename, ".old")) { fprintf(stderr, " WRITE ERROR: %s: ", basename(par->filename)); fprintf(stderr, "File exists\n"); return 0; } f = file_open(par->filename, 1); if (!f) { fprintf(stderr, " WRITE ERROR: %s: ", basename(par->filename)); perror(""); return 0; } par->file_list = PAR_FIX_HEAD_SIZE; par->file_list_size = write_file_entries(0, par->files); par->data = par->file_list + par->file_list_size; if (par->vol_number == 0) { par->data_size = uni_sizeof(par->comment); } else { for (i = 0, p = par->files; p; p = p->next, i++) { if (par->data_size < p->file_size) par->data_size = p->file_size; } } /*\ Calculate set hash \*/ par->num_files = 0; for (i = 0, p = par->files; p; p = p->next) { par->num_files++; if (USE_FILE(p)) i++; } NEW(hashes, i); for (i = 0, p = par->files; p; p = p->next) { if (!USE_FILE(p)) continue; COPY(hashes[i], p->hash, sizeof(md5)); i++; } md5_buffer((char *)hashes, i * sizeof(md5), par->set_hash); free(hashes); if (cmd.loglevel > 1) dump_par(par); par_endian_write(par, &data); file_write(f, &data, PAR_FIX_HEAD_SIZE); write_file_entries(f, par->files); if (par->vol_number == 0) { file_write(f, par->comment, par->data_size); if (cmd.ctrl) { if (!file_add_md5(f, 0x0010, 0x0020, par->data + par->data_size)) { fprintf(stderr, " ERROR: %s:", basename(par->filename)); perror(""); fprintf(stderr, " %-40s - FAILED\n", basename(par->filename)); file_close(f); f = 0; if (!cmd.keep) file_delete(par->filename); } } if (f) file_close(f); } return f; } /*\ |*| Restore missing files with recovery volumes \*/ int restore_files(pfile_t *files, pfile_t *volumes, sub_t *sub) { int N, M, i; xfile_t *in, *out; pfile_t *p, *v, **pp, **qq; int fail = 0; i64 size; pfile_t *mis_f, *mis_v; u16 *path; /*\ Separate out missing files \*/ p = files; size = 0; pp = &files; qq = &mis_f; *pp = *qq = 0; for (i = 1; p; p = p->next, i++) { p->vol_number = i; if (!USE_FILE(p)) continue; if (p->file_size > size) size = p->file_size; if (!find_file(p, 0)) { NEW(*qq, 1); COPY(*qq, p, 1); qq = &((*qq)->next); *qq = 0; } else { NEW(*pp, 1); COPY(*pp, p, 1); (*pp)->next = 0; pp = &((*pp)->next); *pp = 0; } } /*\ Separate out missing volumes \*/ p = volumes; pp = &volumes; qq = &mis_v; *pp = *qq = 0; for (; p; p = p->next) { if (p->vol_number && !(p->f)) { NEW(*qq, 1); COPY(*qq, p, 1); qq = &((*qq)->next); *qq = 0; } else { NEW(*pp, 1); COPY(*pp, p, 1); pp = &((*pp)->next); *pp = 0; } } /*\ Count existing files and volumes \*/ for (N = 0, p = files; p; p = p->next) N++; for (v = volumes; v; v = v->next, N++) N++; /*\ Count missing files and volumes \*/ for (M = 0, p = mis_f; p; p = p->next) M++; for (v = mis_v; v; v = v->next, N++) M++; NEW(in, N + 1); NEW(out, M + 1); /*\ Fill in input files \*/ for (i = 0, p = files; p; p = p->next) { p->f = file_open(p->match->filename, 0); if (!p->f) { fprintf(stderr, " ERROR: %s:", basename(p->match->filename)); perror(""); continue; } in[i].filenr = p->vol_number; in[i].files = 0; in[i].size = p->file_size; in[i].f = p->f; i++; } /*\ Fill in input volumes \*/ for (v = volumes; v; v = v->next) { in[i].filenr = v->vol_number; in[i].files = v->fnrs; in[i].size = v->file_size; in[i].f = v->f; i++; } in[i].filenr = 0; /*\ Fill in output files \*/ for (i = 0, p = mis_f; p; p = p->next) { path = do_sub(p->filename, sub); /*\ Open output file, but check we don't overwrite anything \*/ if (move_away(path, ".bad")) { fprintf(stderr, " ERROR: %s: ", basename(path)); fprintf(stderr, "File exists\n"); fprintf(stderr, " %-40s - NOT RESTORED\n", basename(path)); continue; } p->f = file_open(path, 1); if (!p->f) { fprintf(stderr, " ERROR: %s: ", basename(path)); perror(""); fprintf(stderr, " %-40s - NOT RESTORED\n", basename(path)); continue; } out[i].size = p->file_size; out[i].filenr = p->vol_number; out[i].files = 0; out[i].f = p->f; i++; } /*\ Fill in output volumes \*/ for (v = mis_v; v; v = v->next) { par_t *par; par = create_par_header(v->filename, v->vol_number); if (!par) { fprintf(stderr, " %-40s - FAILED\n", basename(v->match->filename)); continue; } /*\ Copy file list into par file \*/ par->files = files; par->data_size = size; v->f = write_par_header(par); par->files = 0; if (!v->f) { fprintf(stderr, " %-40s - FAILED\n", basename(par->filename)); fail |= 1; free_par(par); continue; } v->match = hfile_add(par->filename); v->filename = v->match->filename; v->file_size = par->data + par->data_size; out[i].size = par->data_size; out[i].filenr = v->vol_number; out[i].files = v->fnrs; out[i].f = v->f; free_par(par); i++; } out[i].filenr = 0; if (!recreate(in, out)) fail |= 1; free(in); free(out); /*\ Check resulting data files \*/ for (p = mis_f; p; p = p->next) { if (!p->f) continue; file_close(p->f); p->f = 0; path = do_sub(p->filename, sub); p->match = hfile_add(path); if (!hash_file(p->match, HASH)) { fprintf(stderr, " ERROR: %s:", basename(path)); perror(""); fprintf(stderr, " %-40s - NOT RESTORED\n", basename(path)); fail |= 1; if (!cmd.keep) file_delete(path); continue; } if ((p->match->file_size == 0) && (p->file_size != 0)) { fprintf(stderr, " %-40s - NOT RESTORED\n", basename(path)); fail |= 1; if (!cmd.keep) file_delete(path); continue; } if (!CMP_MD5(p->match->hash, p->hash)) { fprintf(stderr, " ERROR: %s: Failed md5 check\n", basename(path)); fprintf(stderr, " %-40s - NOT RESTORED\n", basename(path)); fail |= 1; if (!cmd.keep) file_delete(path); continue; } fprintf(stderr, " %-40s - RECOVERED\n", basename(path)); } /*\ Check resulting volumes \*/ for (v = mis_v; v; v = v->next) { if (!v->f) continue; if (!file_add_md5(v->f, 0x0010, 0x0020, v->file_size)) { fprintf(stderr, " %-40s - FAILED\n", basename(v->filename)); fail |= 1; file_close(v->f); v->f = 0; if (!cmd.keep) file_delete(v->filename); continue; } fprintf(stderr, " %-40s - OK\n", basename(v->filename)); } while ((p = files)) { files = p->next; free(p); } while ((p = volumes)) { volumes = p->next; free(p); } while ((p = mis_f)) { mis_f = p->next; free(p); } while ((p = mis_v)) { mis_v = p->next; free(p); } if (fail) { fprintf(stderr, "\nErrors occurred.\n\n"); return -1; } return 1; } par-cmdline/par.h0100644000076500007650000000423607375541663013610 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore \*/ #ifndef PAR_H #define PAR_H #include "types.h" #define PAR_FIX_HEAD_SIZE 0x60 #define FILE_ENTRY_FIX_SIZE 0x38 struct par_s { i64 magic; u32 version; u32 client; md5 control_hash; md5 set_hash; i64 vol_number; i64 num_files; i64 file_list; i64 file_list_size; i64 data; i64 data_size; i64 control_hash_offset; pfile_t *files; pfile_t *volumes; u16 *filename; u16 *comment; file_t f; }; struct pfile_entr_s { i64 size; i64 status; i64 file_size; md5 hash; md5 hash_16k; u16 filename[1]; }; struct pfile_s { pfile_t *next; i64 status; i64 file_size; md5 hash_16k; md5 hash; i64 vol_number; u16 *filename; file_t f; hfile_t *match; u16 *fnrs; }; extern struct cmdline { int action; int loglevel; int volumes; /*\ Number of volumes to create \*/ int pervol : 1; /*\ volumes is actually files per volume \*/ int plus :1; /*\ Turn on or off options (with + or -) \*/ int move :1; /*\ Move away files that are in the way \*/ int fix :1; /*\ Fix files with bad filenames \*/ int usecase :1; /*\ Compare filenames without case \*/ int dupl :1; /*\ Check for duplicate files \*/ int add :1; /*\ Don't add files to PXX volumes \*/ int pxx :1; /*\ Create PXX volumes \*/ int ctrl :1; /*\ Check/create control hash \*/ int keep :1; /*\ Keep broken files \*/ int smart :1; /*\ Try to be smart about filenames \*/ int dash :1; /*\ End of cmdline switches \*/ } cmd; #define ACTION_CHECK 01 /*\ Check PAR files \*/ #define ACTION_RESTORE 02 /*\ Restore missing files \*/ #define ACTION_MIX 03 /*\ Try to use a mix of all PAR files \*/ #define ACTION_ADD 11 /*\ Create a PAR archive ... \*/ #define ACTION_ADDING 12 /*\ ... and add files to it. \*/ #define ACTION_TEXT_UI 20 /*\ Interactive text interface \*/ #define PAR_MAGIC (*((i64 *)"PAR\0\0\0\0\0")) #define IS_PAR(x) (((x).magic) == PAR_MAGIC) #define CMP_MD5(a,b) (!memcmp((a), (b), sizeof(md5))) #define USE_FILE(p) ((p)->status & 0x1) #endif /* PAR_H */ par-cmdline/readoldpar.c0100644000076500007650000000755607375535616015147 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Read old version PAR and PXX files \*/ #include #include #include #include #include #include "par.h" #include "util.h" #include "fileops.h" #include "rs.h" #include "backend.h" /*\ Endianless fixing code \*/ static i64 read_i64(void *data) { int i; i64 r = 0; u8 *ptr = data; for (i = sizeof(i64); --i >= 0; ) { r <<= 8; r += (i64)ptr[i]; } return r; } static u32 read_u32(void *data) { int i; u32 r = 0; u8 *ptr = data; for (i = sizeof(u32); --i >= 0; ) { r <<= 8; r += (u32)ptr[i]; } return r; } static u16 read_u16(void *data) { int i; u16 r = 0; u8 *ptr = data; for (i = sizeof(u16); --i >= 0; ) { r <<= 8; r += (u16)ptr[i]; } return r; } /*\ Read N bytes of little-endian u16s \*/ static void read_u16s(u16 *str, void *data, i64 n) { u8 *ptr = data; while (--n >= 0) { *str = ptr[0] + (ptr[1] << 8); str++; ptr += 2; } } /*\ Check if something is an old style PAR file \*/ int is_old_par(void *data) { if (!memcmp(data, "PAR", 4)) return 1; if (!memcmp(data, "PXX", 4)) return 2; return 0; } /*\ |*| Read in a PAR file entry to a file struct \*/ static i64 read_old_pfile(pfile_t *file, u8 *ptr, u16 *path, i64 pl) { i64 i, l; file->status = read_i64(ptr + 0x08); file->file_size = read_i64(ptr + 0x10); COPY(file->hash, ptr + 0x28, sizeof(md5)); COPY(file->hash_16k, ptr + 0x18, sizeof(md5)); for (i = 0; ptr[0x3A + i] || ptr[0x3A + i + 1]; i += 2) ; l = pl + i; NEW(file->filename, l); COPY(file->filename, path, pl); read_u16s(file->filename + pl, ptr + 0x3A, i); return read_i64(ptr); } static pfile_t * read_old_pfiles(file_t f, i64 size, u16 *path) { pfile_t *files = 0, **fptr = &files; u8 *buf; i64 i, pl; for (pl = i = 0; path[i]; i++) if (path[i] == DIR_SEP) pl = i + 1; NEW(buf, size); size = file_read(f, buf, size); /*\ The list size is at the start of the block \*/ i = 0; /*\ Loop over the entries; the size of an entry is at the start \*/ while (i < size) { CNEW(*fptr, 1); i += read_old_pfile(*fptr, buf + i, path, pl); fptr = &((*fptr)->next); } free(buf); return files; } /*\ read in an old style PAR file. File should point to beginning. \*/ par_t * read_old_par(file_t f, u16 *file, int silent) { u8 buf[8]; int px; par_t par, *r; char *path; memset(&par, 0, sizeof(par)); path = complete_path(stuni(file)); file_read(f, buf, 4); px = is_old_par(buf); if (!px) { file_close(f); return 0; } if (px == 1) px = 0; par.magic = PAR_MAGIC; par.client = 0x02000500; file_read(f, buf, 2); par.version = read_u16(buf); file_read(f, par.set_hash, 16); par.vol_number = 0; if (px) { file_read(f, buf, 2); par.vol_number = read_u16(buf); if (par.version != 0x85) par.vol_number = 1; } file_read(f, buf, 8); par.file_list = read_i64(buf); file_read(f, buf, 8); par.data = read_i64(buf); if (px) { file_read(f, buf, 8); par.data_size = read_i64(buf); } file_read(f, par.control_hash, 16); par.control_hash_offset = px ? 0x40 : 0x36; par.f = f; par.filename = file; if (!silent && !par_control_check(&par)) { file_close(f); return 0; } if (file_seek(f, par.file_list) < 0) { if (!silent) { fprintf(stderr, "Unable to seek in PAR file"); perror(""); } file_close(f); return 0; } par.filename = make_uni_str(path); file_read(f, buf, 8); par.file_list_size = read_i64(buf) - 8; par.files = read_old_pfiles(f, par.file_list_size, par.filename); if (px) { if (par.data != par.file_list + par.file_list_size) file_seek(f, par.data); par.f = f; } else { par.f = 0; file_close(f); } par.volumes = 0; NEW(r, 1); COPY(r, &par, 1); return r; } par-cmdline/readoldpar.h0100644000076500007650000000067607366423413015137 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Read old version PAR and PXX files \*/ #ifndef READOLDPAR_H #define READOLDPAR_H int is_old_par(void *data); par_t * read_old_par(file_t f, u16 *file, int silent); #endif /* READOLDPAR_H */ par-cmdline/rs.c0100644000076500007650000001667307366423413013445 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Reed-Solomon coding \*/ #include #include #include "types.h" #include "fileops.h" #include "rs.h" #include "util.h" #include "par.h" /*\ |*| Calculations over a Galois Field, GF(8) \*/ u8 gl[0x100], ge[0x200]; /*\ Multiply: a*b = exp(log(a) + log(b)) \*/ static int gmul(int a, int b) { if ((a == 0) || (b == 0)) return 0; return ge[gl[a] + gl[b]]; } /*\ Divide: a/b = exp(log(a) - log(b)) \*/ static int gdiv(int a, int b) { if ((a == 0) || (b == 0)) return 0; return ge[gl[a] - gl[b] + 255]; } /*\ Power: a^b = exp(log(a) * b) \*/ static int gpow(int a, int b) { if (a == 0) return 0; return ge[(gl[a] * b) % 255]; } /*\ Initialise log and exp tables using generator x^8 + x^4 + x^3 + x^2 + 1 \*/ void ginit(void) { unsigned int b, l; b = 1; for (l = 0; l < 0xff; l++) { gl[b] = l; ge[l] = b; b += b; if (b & 0x100) b ^= 0x11d; } for (l = 0xff; l < 0x200; l++) ge[l] = ge[l - 0xff]; } /*\ Fill in a LUT \*/ static void make_lut(u8 lut[0x100], int m) { int j; for (j = 0x100; --j; ) lut[j] = ge[gl[m] + gl[j]]; lut[0] = 0; } #define MT(i,j) (mt[((i) * Q) + (j)]) #define IMT(i,j) (imt[((i) * N) + (j)]) #define MULS(i,j) (muls[((i) * N) + (j)]) int recreate(xfile_t *in, xfile_t *out) { int i, j, k, l, M, N, Q, R; u8 *mt, *imt, *muls; u8 buf[0x10000], *work; i64 s, size; i64 perc; ginit(); /*\ Count number of recovery files \*/ for (i = Q = R = 0; in[i].filenr; i++) { if (in[i].files) { R++; /*\ Get max. matrix row size \*/ for (k = 0; in[i].files[k]; k++) { if (in[i].files[k] > Q) Q = in[i].files[k]; } } else { if (in[i].filenr > Q) Q = in[i].filenr; } } N = i; /*\ Count number of volumes to output \*/ for (i = j = M = 0; out[i].filenr; i++) { M++; if (out[i].files) { j++; /*\ Get max. matrix row size \*/ for (k = 0; out[i].files[k]; k++) { if (out[i].files[k] > Q) Q = out[i].files[k]; } } else { if (out[i].filenr > Q) Q = out[i].filenr; } } R += j; Q += j; CNEW(mt, R * Q); CNEW(imt, R * N); /*\ Fill in matrix rows for recovery files \*/ for (i = j = 0; in[i].filenr; i++) { if (!in[i].files) continue; for (k = 0; in[i].files[k]; k++) MT(j, in[i].files[k]-1) = gpow(k+1, in[i].filenr - 1); IMT(j, i) = 1; j++; } /*\ Fill in matrix rows for output recovery files \*/ for (i = 0, l = Q; out[i].filenr; i++) { if (!out[i].files) continue; for (k = 0; out[i].files[k]; k++) MT(j, out[i].files[k]-1) = gpow(k+1, out[i].filenr - 1); --l; /*\ Fudge filenr \*/ out[i].filenr = l + 1; MT(j, l) = 1; j++; } if (cmd.loglevel > 0) { fprintf(stderr, "Matrix input:\n"); for (i = 0; i < R; i++) { fprintf(stderr, "| "); for (j = 0; j < Q; j++) fprintf(stderr, "%02x ", MT(i, j)); fprintf(stderr, "| "); for (j = 0; j < N; j++) fprintf(stderr, "%02x ", IMT(i, j)); fprintf(stderr, "|\n"); } } /*\ Use (virtual) rows from data files to eliminate columns \*/ for (i = 0; in[i].filenr; i++) { if (in[i].files) continue; k = in[i].filenr - 1; /*\ MT would have a 1 at (i, k), IMT a 1 at (i, i) |*| IMT(j,i) -= MT(j,k) * IMT(i,i) (is MT(j, k)) |*| MT(j,k) -= MT(j,k) * MT(i,k) (becomes 0) \*/ for (j = 0; j < R; j++) { IMT(j, i) ^= MT(j, k); MT(j, k) = 0; } } if (cmd.loglevel > 0) { fprintf(stderr, "Matrix after data file elimination:\n"); for (i = 0; i < R; i++) { fprintf(stderr, "| "); for (j = 0; j < Q; j++) fprintf(stderr, "%02x ", MT(i, j)); fprintf(stderr, "| "); for (j = 0; j < N; j++) fprintf(stderr, "%02x ", IMT(i, j)); fprintf(stderr, "|\n"); } } /*\ Eliminate columns using the remaining rows, so we get I. |*| The accompanying matrix will be the inverse \*/ for (i = 0; i < R; i++) { int d, l; /*\ Find first non-zero entry \*/ for (l = 0; (l < Q) && !MT(i, l); l++) ; if (l == Q) continue; d = MT(i, l); /*\ Scale the matrix so MT(i, l) becomes 1 \*/ for (j = 0; j < Q; j++) MT(i, j) = gdiv(MT(i, j), d); for (j = 0; j < N; j++) IMT(i, j) = gdiv(IMT(i, j), d); /*\ Eliminate the column in the other matrices \*/ for (k = 0; k < R; k++) { if (k == i) continue; d = MT(k, l); for (j = 0; j < Q; j++) MT(k, j) ^= gmul(MT(i, j), d); for (j = 0; j < N; j++) IMT(k, j) ^= gmul(IMT(i, j), d); } } if (cmd.loglevel > 0) { fprintf(stderr, "Matrix after gaussian elimination:\n"); for (i = 0; i < R; i++) { fprintf(stderr, "| "); for (j = 0; j < Q; j++) fprintf(stderr, "%02x ", MT(i, j)); fprintf(stderr, "| "); for (j = 0; j < N; j++) fprintf(stderr, "%02x ", IMT(i, j)); fprintf(stderr, "|\n"); } } /*\ Make the multiplication tables \*/ CNEW(muls, M * N); for (i = 0; out[i].filenr; i++) { /*\ File #x: The row IMT(j) for which MT(j,x) = 1 \*/ for (j = 0; j < R; j++) { k = out[i].filenr - 1; if (MT(j, k) != 1) continue; /*\ All other values should be 0 \*/ for (k = 0; !MT(j, k); k++) ; if (k != out[i].filenr - 1) continue; for (k++; (k < Q) && !MT(j, k); k++) ; if (k != Q) continue; break; } /*\ Did we find a suitable row ? \*/ if (j == R) { out[i].size = 0; continue; } for (k = 0; k < N; k++) MULS(i, k) = IMT(j, k); } free(mt); free(imt); if (cmd.loglevel > 0) { fprintf(stderr, "Multipliers:\n"); for (i = 0; i < M; i++) { fprintf(stderr, "| "); for (j = 0; j < N; j++) fprintf(stderr, "%02x ", MULS(i, j)); fprintf(stderr, "|\n"); } } /*\ Check for columns with all-zeroes \*/ for (j = 0; j < N; j++) { for (i = 0; i < M; i++) if (MULS(i, j)) break; /*\ This input file isn't used \*/ if (i == M) in[j].size = 0; } /*\ Find out how much we should process in total \*/ size = 0; for (i = 0; out[i].filenr; i++) if (size < out[i].size) size = out[i].size; /*\ Restore all the files at once \*/ NEW(work, sizeof(buf) * M); perc = 0; fprintf(stderr, "0%%"); fflush(stderr); /*\ Process all files \*/ for (s = 0; s < size; ) { i64 tr, r, q; u8 *p; /*\ Display progress \*/ while (((s * 50) / size) > perc) { perc++; if (perc % 5) fprintf(stderr, "."); else fprintf(stderr, "%lld%%", (perc / 5) * 10); fflush(stderr); } /*\ See how much we should read \*/ memset(work, 0, sizeof(buf) * M); for (i = 0; in[i].filenr; i++) { tr = sizeof(buf); if (tr > (in[i].size - s)) tr = in[i].size - s; if (tr <= 0) continue; r = file_read(in[i].f, buf, tr); if (r < tr) { perror("READ ERROR"); free(muls); free(work); return 0; } for (j = 0; out[j].filenr; j++) { u8 lut[0x100]; if (s >= out[j].size) continue; if (!MULS(j, i)) continue; /*\ Precalc LUT \*/ make_lut(lut, MULS(j, i)); p = work + (j * sizeof(buf)); /*\ XOR it in, passed through the LUTs \*/ for (q = r; --q >= 0; ) p[q] ^= lut[buf[q]]; } } for (j = 0; out[j].filenr; j++) { if (s >= out[j].size) continue; tr = sizeof(buf); if (tr > (out[j].size - s)) tr = out[j].size - s; r = file_write(out[j].f, work + (j * sizeof(buf)), tr); if (r < tr) { perror("WRITE ERROR"); free(muls); free(work); return 0; } } s += sizeof(buf); } fprintf(stderr, "100%%\n"); fflush(stderr); free(muls); free(work); return 1; } par-cmdline/rs.doc0100644000076500007650000002102307366423413013751 0ustar willemwillemDummies guide to Reed-Solomon coding. This text tries to explain all you need to know to implement Reed-Solomon coding for the purpose of adding one or more checksum files to a number of given files. 1 - What can be done with it ? Let's say you have a set of N files you want to protect from damage. You can then create M checksum files. Suppose one or more of the files go missing. You can take the remaining files, add checksum files so you have N files again, and then pass all of those through a routine that restores the missing files. The checksum files don't depend on each other, so any of them will do. You can even create more of them afterward. 2 - How does it work, basically ? You're making checksum files, or restoring missing files, or even a mix of that. Either way, you have N input files, and M output files. You only need two routines. One routine calculates a set of 'multipliers' for each output file there's a list of 'multipliers', one per input file. The other routine reads in bytes from all the input files. For each output file, those bytes are 'multiplied' , 'added' together, and written to the output file. Nothing more to it. 'Adding' and 'subtracting' are both actually a XOR operation. 'Multiplying' is done with a separate function, that takes two bytes, does some lookups in tables, and returns one result byte. You can look in appendix A for more details on this, but all you need to know that this way of multiplying behaves roughly as you would expect. The routine that calculates the multipliers is a bit more complicated. 3 - How are the multipliers calculated ? First, we'll define a function F(i,j): F(i,j) = i^(j-1) That is, i to the power of (j-1), using the special 'multiplication'. You can easily do this with a 'power' function, which also takes two bytes, does some lookups and returns another byte. For calculating the checksum files, the multipliers are very simple. If you number the original files from 1 to N, and the checksum files from 1 to M, then the multiplier from input file i to output file j is F(i,j) 3.1 - Intermezzo This means that the first checksum file will just be all input files added together. F(i,0) = i^0 = 1. So recovering one file with the first checksum file is pretty easy: take the checksum file, and subtract the remaining files. Recovering one file with another checksum file is a bit more complicated, but should still be easy to understand: If you take the checksum file, subtract each remaining file multiplied by F(i,j), you'll be left with the missing file multiplied by its multiplier. So if you divide the whole bunch by that multiplier, you'll get the missing file. If you want to recover more than one file, it gets more complicated. 4 - Multipliers for recovering files If you know a bit of matrix calculus, this is easy. You make a matrix. Each row corresponds to a file you have, each column corresponds to an original file. On each row you put a single 1 if it's an original file (on the original file's column). Otherwise you have a checksum file, and you put the row F(i,j) (where j is the number of the checksum file). If you look at the set of original files, and the set of files you have as vectors, then multiplying the originals by the matrix will get you the files you have. So if you calculate the inverse of the matrix, using gaussian elimination, you can multiply that by the files you have, and get the original files back. Easy, if you know how gaussian elimination works. If not, read on. 4.1 - What is gaussian elimination ? For each file you want to recover, you make two arrays. Both arrays are a series of multipliers. The first array should be used on the original files, and the second array should be used on the files you have now. You fill in the first array with F(i,j), where j is the number of one of the checksum files you have. The second array is filled with zeroes, with a single 1 on the position the checksum file is. This means that both arrays would result in creating the checksum file. Now, we want to transform those arrays so that the first array contains a single 1, on the position of a file we don't have. We take care that each transformantion will be on both arrays, so they would still have the same result. To transform the arrays, you can do two things: - For each original file you have, you can put a 0 at its position in the first array, and subtract the value that was there from the same position in the second array. In other words, you subtract the file, multiplied by a factor, from both results. - You divide both arrays by a certain factor, so you get a 1 at the position of a missing file. Then you subtract both arrays, multiplied by a factor, from another array so that you get a 0 at the position of the 1. After this is done, the first array has a single 1 at the position of a file we don't have, so the result of the first array would be that original file. So will the result of the second array. We can then use the second array as multipliers for the input files. 5 - An example Suppose you have five values: A, B, C, D, E You want to add three checksums to this: X, Y, Z So the equations become: 1: 1*A + 1*B + 1*C + 1*D + 1*E = X 2: 1*A + 2*B + 3*C + 4*D + 5*E = Y 3: 1*A + 4*B + 9*C + 16*D + 25*E = Z Now, we're missing some values: B, C, D We make the following equations: 1: 1*A + 1*B + 1*C + 1*D + 1*E = 0*A + 0*E + 1*X + 0*Y + 0*Z 2: 1*A + 2*B + 3*C + 4*D + 5*E = 0*A + 0*E + 0*X + 1*Y + 0*Z 3: 1*A + 4*B + 9*C + 16*D + 25*E = 0*A + 0*E + 0*X + 0*Y + 1*Z First, we can subtract the A's and E's from both sides: 1: 0*A + 1*B + 1*C + 1*D + 0*E = -1*A + -1*E + 1*X + 0*Y + 0*Z 2: 0*A + 2*B + 3*C + 4*D + 0*E = -1*A + -5*E + 0*X + 1*Y + 0*Z 3: 0*A + 4*B + 9*C + 16*D + 0*E = -1*A +-25*E + 0*X + 0*Y + 1*Z Then, we divide the first row by 1 (it remains the same) 1: 0*A + 1*B + 1*C + 1*D + 0*E = -1*A + -1*E + 1*X + 0*Y + 0*Z And we subtract it from the other rows (multiplied by 2 and 4 respectively: 2: 0*A + 0*B + 1*C + 2*D + 0*E = 1*A + -3*E + -2*X + 1*Y + 0*Z 3: 0*A + 0*B + 5*C + 12*D + 0*E = 3*A +-21*E + -4*X + 0*Y + 1*Z Same goes for the second row (divide by 1, subtract multiplied by 1 and 5): 2: 0*A + 0*B + 1*C + 2*D + 0*E = 1*A + -3*E + -2*X + 1*Y + 0*Z 1: 0*A + 1*B + 0*C + -1*D + 0*E = -2*A + 2*E + 3*X + -1*Y + 0*Z 3: 0*A + 0*B + 0*C + 2*D + 0*E = -2*A + -6*E + 6*X + -5*Y + 1*Z And for the third row (divide by 2, subtract multiplied by -1 and 2): 3: 0*A + 0*B + 0*C + 1*D + 0*E = -1*A + -3*E + 3*X + -2.5*Y + 0.5*Z 1: 0*A + 1*B + 0*C + 0*D + 0*E = -3*A + -1*E + 6*X + -3.5*Y + 0.5*Z 2: 0*A + 0*B + 1*C + 0*D + 0*E = 3*A + 3*E + -8*X + 6 *Y + -1 *Z We can simplify this to: B = -3*A + -1*E + 6*X + -3.5*Y + 0.5*Z C = 3*A + 3*E + -8*X + 6 *Y + -1 *Z D = -1*A + -3*E + 3*X + -2.5*Y + 0.5*Z Appendix A - Galois Fields All through this document, we were doing calculations on bytes. Because we don't want roundoff/cutoff errors and whatnot, we need to have a form of 'calculation' that behaves like we would expect, but stays within the 0-255 range. This is called a Galois Field. Basically, adding and subtracting are both done by XORing two bytes: a '+' b = a '-' b = a XOR b Multiplying and dividing are done with tables. We precalculate a table with exponents, with an accompanying reverse-lookup table: a '*' b = exp(log(a) + log(b)) a '/' b = exp(log(a) - log(b)) Exponentiating is also easy, using the same tables: a '^' b = exp(log(a) * b) The table wraps around after 255 bytes, (note: 255, not 256) and you also need to have special cases for a = 0 and b = 0, so the routines become: multiply(a, b) { if (a == 0) return 0; if (b == 0) return 0; i = log[a] + log[b]; if (i > 255) i = i - 255; return exp[i]; } divide(a, b) { if (a == 0) return 0; if (b == 0) return 0; i = log[a] - log[b]; if (i < 0) i = i + 255; return exp[i]; } power(a, b) { if (a == 0) return 0; i = log[a] * b; while (i > 255) i = i - 255; return exp[i]; } Now, all we need is a routine to set up the exp and log tables. This is rather complicated to explain, so here's the routine that does it: init() { b = 1; for (l = 0; l < 255; l++) { exp[l] = b; log[b] = l; b += b; if (b > 255) b ^= 285; } exp[255] = exp[0]; } Basically, b is doubled each time, and if it gets too large, it's XORed with a 'magic' number. The 'magic' number is chosen so that b will have all possible values. If you really want to know, it's binary 100011101, which corresponds to x^8 + x^4 + x^3 + x^2 + 1. Willem. par-cmdline/rs.h0100644000076500007650000000073207366423413013437 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Reed-Solomon coding \*/ #ifndef REEDSOLOMON_H #define REEDSOLOMON_H typedef struct xfile_s xfile_t; struct xfile_s { i64 size; file_t f; u16 filenr; u16 *files; }; int recreate(xfile_t *in, xfile_t *out); #endif par-cmdline/fileops.c0100644000076500007650000001466207400025007014440 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File operations, in a separate file because these are probably |*| going to cause the most portability problems. \*/ #include #include #include #include #include #include #include #include #include #include #include "md5.h" #include "fileops.h" #include "util.h" #include "par.h" /*\ |*| Translate an ASCII string into unicode, write it onto the buffer \*/ void unistr(const char *str, u16 *buf) { do *buf++ = *str; while (*str++); } /*\ |*| Translate an ASCII string into unicode |*| Returns an allocated string \*/ u16 * make_uni_str(const char *str) { u16 *uni; NEW(uni, strlen(str) +1); unistr(str, uni); return uni; } /*\ |*| Translate a unicode string into ASCII |*| Returns static string, which is overwritten at next call \*/ char * stuni(const u16 *str) { i64 i; static char *buf = 0; static i64 bufsize = 0; /*\ Count the length \*/ for (i = 0; str[i]; i++) ; if ((i + 1) > bufsize) { bufsize = i + 1; RENEW(buf, bufsize); } /*\ For now, just copy the low byte \*/ for (i = 0; str[i]; i++) buf[i] = str[i]; buf[i] = 0; return buf; } /*\ |*| Translate an ASCII string into unicode |*| Returns static string, which is overwritten at next call \*/ u16 * unist(const char *str) { i64 i; static u16 *buf = 0; static i64 bufsize = 0; /*\ Count the length \*/ for (i = 0; str[i]; i++) ; if ((i + 1) > bufsize) { bufsize = i + 1; RENEW(buf, bufsize); } /*\ For now, just copy the low byte \*/ for (i = 0; str[i]; i++) buf[i] = str[i]; buf[i] = 0; return buf; } /*\ |*| Translate an md5 hash into ASCII |*| Returns static string, which is overwritten at next call \*/ char * stmd5(const md5 hash) { int i; static char buf[33]; for (i = 0; i < 16; i++) { buf[i * 2] = HEXDIGIT((hash[i] >> 4) & 0xF); buf[i * 2 + 1] = HEXDIGIT(hash[i] & 0xF); } buf[i * 2] = 0; return buf; } i64 uni_copy(u16 *dst, u16 *src, i64 n) { i64 i; for (i = 0; src[i] && (i < (n - 1)); i++) dst[i] = src[i]; dst[i] = 0; return i; } u16 * unicode_copy(u16 *str) { u16 *ret, *p; if (!str) { NEW(ret, 1); ret[0] = 0; return ret; } for (p = str; *p; p++) ; NEW(ret, (p - str) + 1); COPY(ret, str, (p - str) + 1); return ret; } int file_rename(u16 *src, u16 *dst) { int i; char *s, *d; s = stuni(dst); NEW(d, strlen(s) + 1); strcpy(d, s); s = stuni(src); i = rename(s, d); free(d); return i; } static int do_seek(file_t f) { if (f->off == f->s_off) return 0; if (!f->f) return 0; if (fseek(f->f, f->s_off, SEEK_SET) < 0) return -1; f->off = f->s_off; return 0; } static int do_close(file_t f) { int i; if (!f->f) return 0; i = fclose(f->f); f->f = 0; return i; } static int do_open(file_t f) { static file_t openfiles = 0; int i; while (!f->f) { /*\ This is so complicated to make sure we don't overwrite \*/ i = open(f->name, f->wr ? O_RDWR|O_CREAT|O_EXCL : O_RDONLY, 0666); if (i >= 0) f->f = fdopen(i, f->wr ? "w+b" : "rb"); if (!f->f) { if ((errno != EMFILE) && (errno != ENFILE)) return -1; if (!openfiles) return -1; while (!openfiles->f) openfiles = openfiles->next; do_close(openfiles); openfiles = openfiles->next; } else { f->off = 0; if (!f->wr) { f->next = openfiles; openfiles = f; } } } return do_seek(f); } file_t file_open_ascii(const char *path, int wr) { file_t f; CNEW(f, 1); NEW(f->name, strlen(path) + 1); strcpy(f->name, path); f->wr = wr; return f; } file_t file_open(const u16 *path, int wr) { return file_open_ascii(stuni(path), wr); } int file_close(file_t f) { int i; if (!f) return 0; i = do_close(f); free(f->name); free(f); return i; } int file_exists(u16 *file) { FILE *f; f = fopen(stuni(file), "rb"); if (!f) return (errno != ENOENT); fclose(f); return 1; } int file_delete(u16 *file) { return remove(stuni(file)); } int file_seek(file_t f, i64 off) { f->s_off = off; return do_seek(f); } char * complete_path(char *path) { #if 0 /* def PATH_MAX */ static u8 buf[PATH_MAX]; if (realpath(path, buf)) return buf; #endif return path; } /*\ Read a directory. |*| Returns a linked list of file entries. \*/ hfile_t * read_dir(char *dir) { DIR *d; struct dirent *de; hfile_t *rd = 0, **rdptr = &rd; u16 *p; int l, i; char *dr, *dp, *ds; dir = complete_path(dir); l = 0; for (i = 0; dir[i]; i++) if (dir[i] == DIR_SEP) l = i + 1; NEW(dr, l + 1); memcpy(dr, dir, l); dr[l] = 0; d = opendir(l ? dr : "."); if (d) { while ((de = readdir(d))) { CNEW(*rdptr, 1); NEW(p, l + strlen(de->d_name) + 1); unistr(dr, p); unistr(de->d_name, p + l); (*rdptr)->filename = p; rdptr = &((*rdptr)->next); } closedir(d); } free(dr); return rd; } i64 file_read(file_t f, void *buf, i64 n) { i64 i; if (!f) return 0; if (do_open(f) < 0) return 0; i = fread(buf, 1, n, f->f); if (i > 0) { f->off += i; f->s_off = f->off; } return i; } i64 file_write(file_t f, void *buf, i64 n) { i64 i; if (!f) return 0; if (do_open(f) < 0) return 0; i = fwrite(buf, 1, n, f->f); if (i > 0) { f->off += i; f->s_off = f->off; } return i; } /*\ Calculate md5 sums on a file \*/ i64 file_md5(u16 *file, md5 block) { FILE *f; i64 i; f = fopen(stuni(file), "rb"); if (!f) return 0; i = md5_stream(f, block); fclose(f); return i; } int file_md5_buffer(u16 *file, md5 block, u8 *buf, i64 size) { file_t f; i64 s; f = file_open(file, 0); if (!f) return 0; s = file_read(f, buf, size); file_close(f); if (s < 0) return 0; return (md5_buffer(buf, s, block) != 0); } /*\ Calculate the md5sum of a file from offset 'off', |*| put it at offset 'md5off' |*| Also, check the file size. Return 0 on failure. \*/ int file_add_md5(file_t f, i64 md5off, i64 off, i64 len) { md5 hash; i64 i; if (!f) return 0; f->s_off = off; if (do_open(f) < 0) return 0; i = md5_stream(f->f, hash); if (i < 0) return 0; f->off += i; f->s_off = f->off; /*\ Filepointer should be at EOF now \*/ if (f->off != len) return 0; if (file_seek(f, md5off) < 0) return 0; if (file_write(f, hash, sizeof(hash)) < 0) return 0; return 1; } /*\ Get the md5sum over part of a file. \*/ int file_get_md5(file_t f, i64 off, md5 block) { i64 i, tmp = f->off; f->s_off = off; if (do_open(f) < 0) return 0; i = md5_stream(f->f, block); f->s_off = tmp; return (i != 0); } par-cmdline/types.h0100644000076500007650000000111407375527652014164 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| Type definitions \*/ #ifndef TYPES_H #define TYPES_H #include typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef signed long long i64; typedef u8 md5[16]; typedef struct par_s par_t; typedef struct pxx_s pxx_t; typedef struct pfile_entr_s pfile_entr_t; typedef struct pfile_s pfile_t; typedef struct hfile_s hfile_t; typedef struct sub_s sub_t; typedef struct file_s *file_t; #endif /* TYPES_H */ par-cmdline/ui_text.c0100644000076500007650000002021607375544743014500 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Text User Interface \*/ #include #include #include #include #include "interface.h" #include "fileops.h" static u16 **parlist = 0; static u16 **filelist = 0; static u16 **dirlist = 0; enum { CMD_FLAGS, CMD_SETFLAGS, CMD_UNSETFLAGS, CMD_LOAD, CMD_SEARCH, CMD_UNLOAD, CMD_PARLIST, CMD_FILELIST, CMD_DIRLIST, CMD_CHECK, CMD_FIND, CMD_FIXNAME, CMD_GETSTATUS, CMD_SETSTATUS, CMD_RECOVER, CMD_ADDFILE, CMD_REMOVEFILE, CMD_ADDPARS, CMD_CREATE, CMD_SETSMART, CMD_HELP, CMD_QUIT, CMD_UNKNOWN, CMD_AMBIGUOUS }; static struct cmds { char *str; int cmd; } cmds[] = { { "FLAGS", CMD_FLAGS }, { "SETFLAGS", CMD_SETFLAGS }, { "UNSETFLAGS", CMD_UNSETFLAGS }, { "LOAD", CMD_LOAD }, { "SEARCH", CMD_SEARCH }, { "UNLOAD", CMD_UNLOAD }, { "PARLIST", CMD_PARLIST }, { "FILELIST", CMD_FILELIST }, { "DIRLIST", CMD_DIRLIST }, { "CHECK", CMD_CHECK }, { "FIND", CMD_FIND }, { "FIXNAME", CMD_FIXNAME }, { "GETSTATUS", CMD_GETSTATUS }, { "SETSTATUS", CMD_SETSTATUS }, { "RECOVER", CMD_RECOVER }, { "ADDFILE", CMD_ADDFILE }, { "REMOVEFILE", CMD_REMOVEFILE }, { "ADDPARS", CMD_ADDPARS }, { "CREATE", CMD_CREATE }, { "SETSMART", CMD_SETSMART }, { "HELP", CMD_HELP }, { "QUIT", CMD_QUIT } }; #define NO_CMDS (sizeof(cmds) / sizeof(*cmds)) static void print_help(void) { printf( "FLAGS : Show the current flags (with numbers).\n" "SETFLAGS : Set flag number .\n" "UNSETFLAGS : Unset flag number .\n" "SETSMART [ ] : Setup handling of consistently misnamed files.\n" "LOAD : Add a (new) PAR file to the current list.\n" "SEARCH : Search for PAR files matching the current filelist.\n" "UNLOAD : Remove a PAR file from the list.\n" "PARLIST : Show the current list of PAR files.\n" "FILELIST : Show the current list of data files.\n" "DIRLIST : Show the currently cached directory entries.\n" "CHECK : Check the MD5sum of a file.\n" "FIND : Find a file by its filename.\n" "FIXNAME [] : Fix faulty filenames [of ].\n" "GETSTATUS : Get the status bits of an entry.\n" "SETSTATUS : Set the status bits of an entry.\n" "RECOVER [] : Recover missing files [only ]\n" "ADDFILE : Add a data file to the current filelist.\n" "REMOVEFILE : Remove a data file from the current filelist.\n" "ADDPARS : Add new PAR files until there are .\n" "CREATE [] : Create PAR files [only ].\n" "HELP : Show this help.\n" "QUIT : Quit.\n" ); } static int cc = '\n'; #define EOL(x) (((x) == '\n') || ((x) == EOF)) static void sort_cmds(void) { int i, j, k; k = NO_CMDS - 1; do { for (i = j = 0; i < k; i++) { if (strcmp(cmds[i].str, cmds[i + 1].str) > 0) { struct cmds tmp; tmp = cmds[i + 1]; cmds[i + 1] = cmds[i]; cmds[i] = tmp; j = i; } } k = j; } while (k > 0); } static int get_cmd(void) { struct cmds *l, *r; int c; int idx; do { while (!EOL(cc)) cc = getchar(); if (cc == EOF) return CMD_QUIT; putchar('>'); fflush(stdout); l = cmds; r = cmds + NO_CMDS - 1; for (idx = 0;; idx++) { cc = getchar(); if (!isalnum(cc)) break; c = toupper(cc); while (c > l->str[idx]) if (++l > r) return CMD_UNKNOWN; while (c < r->str[idx]) if (--r < l) return CMD_UNKNOWN; } } while (idx == 0); if (r > l) return CMD_AMBIGUOUS; return l->cmd; } static int bufidx = 0; static u16 * buf_add(u16 c) { static u16 *buf = 0; static int bufsz = 0; if (bufidx >= bufsz) { bufsz += 256; buf = realloc(buf, bufsz * sizeof(*buf)); } buf[bufidx++] = c; return buf; } static u16 * get_str(void) { bufidx = 0; while (isspace(cc)) { if (EOL(cc)) return buf_add(0); cc = getchar(); } do { buf_add(cc); cc = getchar(); } while (!EOL(cc)); return buf_add(0); } static i64 get_number(void) { i64 ret; int base = 10; while (isspace(cc)) { if (EOL(cc)) return 0; cc = getchar(); } ret = 0; if (cc == '0') { base = 8; cc = getchar(); } if ((cc == 'x') || (cc == 'X') || (cc == '$')) { base = 16; cc = getchar(); } for (;;) { int c; c = toupper(cc); if (c >= 'a') c -= 'a'; else c -= '0'; if ((c < 0) || (c >= base)) return ret; ret = ret * base + c; cc = getchar(); } } static u16 * get_entry(u16 **list) { i64 num, i; i = get_number(); if (!list) return 0; for (num = 0; list[num]; num++) ; if ((i < 0) || (i > num)) i = 0; if (i == 0) return 0; return list[i - 1]; } static void print_errcode(int code) { static char *err_list[] = { "OK", "ERROR: File error", "ERROR: Does not exist", "ERROR: Not found", "ERROR: Corrupt", "ERROR: Failed", "ERROR: Already loaded", "ERROR: Not Implemented", "ERROR: Name Clash", "ERROR: Invalid Argument" }; printf("%s\n", err_list[code]); } static void print_flags(int flags) { static char *flag_list[] = { "MOVE", "CASE", "CTRL", "KEEP", }; unsigned int i; for (i = 0; i < (sizeof(flag_list) / sizeof(*flag_list)); i++) { if (i > 0) printf(", "); if (!(flags & (1 << i))) printf("NO"); printf("%s(%d)", flag_list[i], 1 << i); } printf("\n"); } static void print_string(u16 *str) { while (*str) putchar(*str++); putchar('\n'); } static void print_number(i64 num) { printf("0x%llx\n", num); } static void print_list(u16 **list) { int i; for (i = 1; *list; list++, i++) { printf("%3d: ", i); print_string(*list); } } /*\ Parse commands until QUIT is read \*/ void ui_text(void) { u16 *e; sort_cmds(); cc = '\n'; for (;;) switch (get_cmd()) { case CMD_FLAGS: print_flags(par_flags()); break; case CMD_SETFLAGS: print_flags(par_setflags(get_number())); break; case CMD_UNSETFLAGS: print_flags(par_unsetflags(get_number())); break; case CMD_LOAD: print_errcode(par_load(get_str())); break; case CMD_SEARCH: print_errcode(par_search(get_number())); break; case CMD_UNLOAD: print_errcode(par_unload(get_entry(parlist))); break; case CMD_PARLIST: if (parlist) free(parlist); parlist = par_parlist(); print_list(parlist); break; case CMD_FILELIST: if (filelist) free(filelist); filelist = par_filelist(); print_list(filelist); break; case CMD_DIRLIST: if (dirlist) free(dirlist); dirlist = par_dirlist(); print_list(dirlist); break; case CMD_CHECK: e = get_entry(filelist); if (e) { print_errcode(par_check(e)); } else if (filelist) { int i; for (i = 0; filelist[i]; i++) { printf("CHECK "); print_string(filelist[i]); print_errcode(par_check(filelist[i])); } } else { printf("ERROR: No filelist.\n"); } break; case CMD_FIND: e = get_entry(filelist); if (e) { print_string(par_find(e)); } else if (filelist) { int i; for (i = 0; filelist[i]; i++) { printf("FIND "); print_string(filelist[i]); print_string(par_find(filelist[i])); } } else { printf("ERROR: No filelist.\n"); } break; case CMD_FIXNAME: print_errcode(par_fixname(get_entry(filelist))); break; case CMD_GETSTATUS: print_number(par_getstatus(get_entry(filelist))); break; case CMD_SETSTATUS: e = get_entry(filelist); print_errcode(par_setstatus(e, get_number())); break; case CMD_RECOVER: print_errcode(par_recover(get_entry(filelist))); break; case CMD_ADDFILE: print_errcode(par_addfile(get_str())); break; case CMD_REMOVEFILE: print_errcode(par_removefile(get_entry(filelist))); break; case CMD_ADDPARS: e = get_entry(parlist); print_errcode(par_addpars(e, get_number())); break; case CMD_CREATE: print_errcode(par_create(get_entry(parlist))); break; case CMD_SETSMART: e = get_entry(filelist); print_errcode(par_setsmart(e, e ? get_str() : 0)); break; case CMD_QUIT: return; case CMD_HELP: print_help(); break; case CMD_AMBIGUOUS: printf("Ambiguous command.\n"); break; default: printf("Unknown command.\n"); break; } } par-cmdline/util.h0100644000076500007650000000076407366427530014001 0ustar willemwillem/*\ |*| Some utility macro's |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) \*/ #ifndef UTIL_H #define UTIL_H #define HEXDIGIT(i) (((i) + (((i) < 0xA) ? '0' : ('a' - 0xA)))) #define NEW(ptr, size) ((ptr) = (malloc(sizeof(*(ptr)) * (size)))) #define CNEW(ptr, size) ((ptr) = (calloc(sizeof(*(ptr)), (size)))) #define RENEW(ptr, size) ((ptr) = (realloc((ptr), sizeof(*(ptr)) * (size)))) #define COPY(tgt, src, nel) (memcpy((tgt), (src), ((nel) * sizeof(*(tgt))))) #endif /* UTIL_H */ par-cmdline/fileops.h0100644000076500007650000000272107370111141014437 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File operations, in a separate file because these are probably |*| going to cause the most portability problems. \*/ #ifndef FILEOPS_H #define FILEOPS_H #define DIR_SEP '/' #include #include #include #include "types.h" struct hfile_s { hfile_t *next; md5 hash_16k; md5 hash; i64 file_size; i64 magic; u16 *filename; char *dir; u8 hashed; }; struct file_s { file_t next; FILE *f; char *name; i64 off, s_off; int wr; }; #define HASH16K 1 #define HASH 2 void unistr(const char *str, u16 *buf); u16 *make_uni_str(const char *str); char *stuni(const u16 *str); u16 *unist(const char *str); char *stmd5(const md5 hash); i64 uni_copy(u16 *dst, u16 *src, i64 n); u16 * unicode_copy(u16 *str); int file_rename(u16 *src, u16 *dst); file_t file_open(const u16 *path, int wr); file_t file_open_ascii(const char *path, int wr); int file_close(file_t f); int file_exists(u16 *file); int file_delete(u16 *file); int file_seek(file_t f, i64 off); i64 file_md5(u16 *file, md5 block); int file_md5_buffer(u16 *file, md5 block, u8 *buf, i64 size); int file_add_md5(file_t f, i64 md5off, i64 off, i64 len); int file_get_md5(file_t f, i64 off, md5 block); char * complete_path(char *path); hfile_t *read_dir(char *dir); i64 file_read(file_t f, void *buf, i64 n); i64 file_write(file_t f, void *buf, i64 n); #endif par-cmdline/rwpar.h0100644000076500007650000000130007375534466014151 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Read and write PAR files \*/ #ifndef READWRITEPAR_H #define READWRITEPAR_H #include "types.h" #include "par.h" par_t * create_par_header(u16 *file, i64 vol); par_t * read_par_header(u16 *file, int create, i64 vol, int silent); void free_file_list(pfile_t *list); void free_par(par_t *par); file_t write_par_header(par_t *par); int restore_files(pfile_t *files, pfile_t *volumes, sub_t *sub); void dump_par(par_t *par); #endif /* READWRITEPAR_H */ par-cmdline/backend.c0100644000076500007650000003644607400017773014405 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Backend operations \*/ #include #include #include #include #include #include "util.h" #include "rwpar.h" #include "fileops.h" #include "rs.h" #include "readoldpar.h" #include "backend.h" #include "md5.h" /*\ |*| Static directory list \*/ hfile_t *hfile = 0; char * basename(u16 *path) { u16 *ret; for (ret = path; *path; path++) if (*path == DIR_SEP) ret = path + 1; return stuni(ret); } /*\ |*| Order two unicode strings (caseless) |*| Return 1 if a > b \*/ int unicode_gt(u16 *a, u16 *b) { for (; *a || *b; a++, b++) { if (tolower(*a) > tolower(*b)) return 1; if (tolower(*a) < tolower(*b)) return 0; } return 0; } /*\ |*| Compare two unicode strings |*| -1: Strings are not equal |*| 0: Strings are equal |*| 1: Strings only differ in upper/lowercase (doesn't happen with cmd.usecase) \*/ int unicode_cmp(u16 *a, u16 *b) { for (; *a == *b; a++, b++) if (!*a) return 0; if (!cmd.usecase) for (; tolower(*a) == tolower(*b); a++, b++) if (!*a) return 1; return -1; } /*\ |*| Rename a file, and move the directory entry with it. \*/ static int rename_file(u16 *src, u16 *dst) { hfile_t *p; int n; fprintf(stderr, " Rename: %s", basename(src)); fprintf(stderr, " -> %s", basename(dst)); n = file_rename(src, dst); if (n) { perror(" "); return n; } fprintf(stderr, "\n"); /*\ Change directory entries \*/ for (p = hfile; p; p = p->next) { if (!unicode_cmp(p->filename, src)) { free(p->filename); p->filename = unicode_copy(dst); } } return 0; } hfile_t * hfile_add(u16 *filename) { int i; hfile_t **pp; for (pp = &hfile; *pp; pp = &((*pp)->next)) ; CNEW(*pp, 1); (*pp)->filename = unicode_copy(filename); return (*pp); } /*\ |*| Read in a directory and add it to the static directory structure \*/ void hash_directory(char *dir) { hfile_t *p, *q, **pp; /*\ only add new items \*/ for (p = read_dir(dir); p; ) { for (pp = &hfile; *pp; pp = &((*pp)->next)) if (!unicode_cmp(p->filename, (*pp)->filename)) break; if (*pp) { q = p; p = p->next; free(q); } else { *pp = p; p = p->next; (*pp)->next = 0; } } } /*\ |*| Calculate md5 sums for a file, but only once \*/ int hash_file(hfile_t *file, char type) { i64 s; u8 buf[16384]; if (type < HASH16K) return 1; if (file->hashed < HASH16K) { if (!file_md5_buffer(file->filename, file->hash_16k, buf, sizeof(buf))) return 0; file->hashed = HASH16K; COPY(&file->magic, buf, 1); } if (type < HASH) return 1; if (file->hashed < HASH) { s = file_md5(file->filename, file->hash); if (s >= 0) { file->hashed = HASH; if (!file->file_size) file->file_size = s; } } return 1; } /*\ |*| Rename a file so it's not in the way |*| Append '.bad' because I assume a file that's in the way |*| is a corrupt version. \*/ int move_away(u16 *file, const u8 *ext) { u16 *fn; int l, i; if (!file_exists(file)) return 0; if (!cmd.move) return -1; for (i = 0; file[i]; i++) ; l = i + strlen(ext); NEW(fn, l + 1); COPY(fn, file, i); for (; i <= l; i++) fn[i] = *ext++; i = 0; while (file_exists(fn)) { if (i > 99) return -1; fn[l-2] = '0' + (i / 10); fn[l-1] = '0' + (i % 10); i++; } i = rename_file(file, fn); free(fn); return i; } /*\ |*| Rename a file, but first move away the destination \*/ int rename_away(u16 *src, u16 *dst) { if (move_away(dst, ".bad")) { fprintf(stderr, " Rename: %s", basename(src)); fprintf(stderr, " -> %s : ", basename(dst)); fprintf(stderr, "File exists\n"); return -1; } else if (rename_file(src, dst)) { return -1; } else { return 0; } } /*\ |*| Find a file in the static directory structure that matches the md5 sums. |*| Try matching filenames first \*/ int find_file(pfile_t *file, int displ) { hfile_t *p; int cm, corr = 0; if (file->match) return 1; /*\ Check filename (caseless) and then check md5 hash \*/ for (p = hfile; p; p = p->next) { cm = unicode_cmp(p->filename, file->filename); if (cm < 0) continue; if (!hash_file(p, HASH)) { if (displ) { fprintf(stderr, " ERROR: %s", basename(p->filename)); perror(" "); } corr = 1; continue; } if (CMP_MD5(p->hash, file->hash)) { if (!cm || !file->match) file->match = p; continue; } if (displ) fprintf(stderr, " ERROR: %s: Failed md5 sum\n", basename(p->filename)); corr = 1; } if (file->match) { if (displ) fprintf(stderr, " %-40s - OK\n", basename(file->filename)); if (!displ || !cmd.dupl) return 1; } /*\ Try to match md5 hash on all files \*/ for (p = hfile; p; p = p->next) { if (file->match == p) continue; if (!hash_file(p, HASH16K)) continue; if (!CMP_MD5(p->hash_16k, file->hash_16k)) continue; if (!hash_file(p, HASH)) continue; if (!CMP_MD5(p->hash, file->hash)) continue; if (!file->match) { file->match = p; if (displ) { fprintf(stderr, " %-40s - FOUND", basename(file->filename)); fprintf(stderr, ": %s\n", basename(p->filename)); } if (!displ || !cmd.dupl) return 1; } fprintf(stderr, " Duplicate: %s", stuni(file->match->filename)); fprintf(stderr, " == %s\n", basename(p->filename)); } if (!file->match && displ) fprintf(stderr, " %-40s - %s\n", basename(file->filename), corr ? "CORRUPT" : "NOT FOUND"); return (file->match != 0); } /*\ |*| Find a file in the static directory structure \*/ hfile_t * find_file_name(u16 *path, int displ) { hfile_t *p, *ret = 0; hash_directory(stuni(path)); path = unist(complete_path(stuni(path))); /*\ Check filename (caseless) and then check md5 hash \*/ for (p = hfile; p; p = p->next) { switch (unicode_cmp(p->filename, path)) { case 1: if (ret) break; case 0: ret = p; } } if (!ret && displ) fprintf(stderr, " %-40s - NOT FOUND\n", basename(path)); return ret; } /*\ |*| Find a volume in the static directory structure. |*| Base the name on the given filename and volume number. |*| Create it if it's not found. \*/ hfile_t * find_volume(u16 *name, i64 vol) { u16 *filename; i64 i; hfile_t *p, *ret = 0; int nd, v; if (vol < 1) return 0; nd = 2; for (v = 100; vol >= v; v *= 10) nd++; for (i = 0; name[i]; i++) ; if ((name[i-1] < '0') || (name[i-1] > '9')) { i = i - 2; } else { while ((name[i-1] >= '0') && (name[i-1] <= '9')) i--; } i += nd; NEW(filename, i + 1); uni_copy(filename, name, i); filename[i] = 0; v = vol; while (--nd >= 0) { filename[--i] = '0' + v % 10; v /= 10; } for (p = hfile; p; p = p->next) { switch (unicode_cmp(p->filename, filename)) { case 1: if (ret) break; case 0: ret = p; } } if (!ret) ret = hfile_add(filename); free(filename); return ret; } /*\ |*| Check if the two file lists a and b match. |*| File lists match if they are equal, |*| only counting files which are stored in the parity volume. \*/ static int files_match(pfile_t *a, pfile_t *b, int part) { if (part) { /*\ Check if the lists overlap \*/ pfile_t *p; for (; a; a = a->next) { if (!USE_FILE(a)) continue; for (p = b; p; p = p->next) { if (!USE_FILE(p)) continue; if (a->file_size != b->file_size) continue; if (!CMP_MD5(a->hash, b->hash)) continue; return 1; } } return 0; } /*\ Check if the lists match \*/ while (a || b) { if (a && !USE_FILE(a)) { a = a->next; continue; } if (b && !USE_FILE(b)) { b = b->next; continue; } if (!a || !b) return 0; if (a->file_size != b->file_size) return 0; if (!CMP_MD5(a->hash, b->hash)) return 0; a = a->next; b = b->next; } return 1; } u16 * file_numbers(pfile_t **list, pfile_t **files) { int i, j; pfile_t *p, **qq; u16 *fnrs; for (i = 0, p = *files; p; p = p->next) if (USE_FILE(p)) i++; NEW(fnrs, i + 1); for (i = 0; *files; ) { /*\ Look for a match \*/ for (j = 1, qq = list; *qq; qq = &((*qq)->next), j++) { if ((*files)->file_size != (*qq)->file_size) continue; if (!CMP_MD5((*files)->hash, (*qq)->hash)) continue; break; } if (USE_FILE(*files)) fnrs[i++] = j; /*\ No match ? Move the file entry to the tail of the list \*/ if (!*qq) { *qq = *files; *files = (*files)->next; (*qq)->next = 0; } else { files = &((*files)->next); } } fnrs[i] = 0; return fnrs; } int par_control_check(par_t *par) { md5 hash; if (!cmd.ctrl) return 1; /*\ Check version number \*/ if (par->version > 0x0001ffff) { fprintf(stderr, "%s: PAR Version mismatch! (%x.%x)\n", basename(par->filename), par->version >> 16, (par->version & 0xffff) >> 8); return 0; } /*\ Check md5 control hash \*/ if ((!file_get_md5(par->f, par->control_hash_offset, hash) || !CMP_MD5(par->control_hash, hash))) { fprintf(stderr, "%s: PAR file corrupt:" "control hash mismatch!\n", basename(par->filename)); return 0; } return 1; } /*\ |*| Find all volumes that have the same set of files, |*| but different volume numbers \*/ int find_volumes(par_t *par, int tofind) { int m; pfile_t *v; hfile_t *p; par_t *tmp; v = 0; if (par->vol_number) { CNEW(v, 1); v->match = find_file_name(par->filename, 1); v->vol_number = par->vol_number; v->file_size = par->data_size; v->fnrs = file_numbers(&par->files, &par->files); v->f = par->f; v->next = par->volumes; par->volumes = v; } else { file_close(par->f); } par->f = 0; for (m = 0, v = par->volumes; v; v = v->next) m++; if (m >= tofind) return m; fprintf(stderr, "\nLooking for %sPXX volumes:\n", m ? "additional " : ""); for (p = hfile; p; p = p->next) { if ((p->hashed >= HASH16K) && !IS_PAR(*p) && !is_old_par(&p->magic)) continue; tmp = read_par_header(p->filename, 0, 0, 1); if (!tmp) continue; if (tmp->vol_number && files_match(par->files, tmp->files, 0)) { for (v = par->volumes; v; v = v->next) if (v->vol_number == tmp->vol_number) break; if (v) continue; if (!par_control_check(tmp)) { fprintf(stderr, " %-40s - CORRUPT\n", basename(p->filename)); free_par(tmp); continue; } CNEW(v, 1); v->match = p; v->vol_number = tmp->vol_number; v->file_size = tmp->data_size; v->fnrs = file_numbers(&par->files, &tmp->files); v->f = tmp->f; tmp->f = 0; v->next = par->volumes; par->volumes = v; m++; fprintf(stderr, " %-40s - OK\n", basename(p->filename)); } free_par(tmp); if (m >= tofind) break; } return m; } /*\ |*| Find all volumes that contain (some of) the given files. \*/ void find_par_files(pfile_t **volumes, pfile_t **files, int part) { pfile_t *v, **vv; hfile_t *p; par_t *tmp; for (p = hfile; p; p = p->next) { if ((p->hashed >= HASH16K) && !IS_PAR(*p) && !is_old_par(&p->magic)) continue; for (v = *volumes; v; v = v->next) if (!unicode_cmp(v->filename, p->filename)) break; if (v) continue; tmp = read_par_header(p->filename, 0, 0, 1); if (!tmp) continue; if (tmp->vol_number && files_match(*files, tmp->files, part)) { if (!par_control_check(tmp)) { free_par(tmp); continue; } CNEW(v, 1); v->match = p; v->vol_number = tmp->vol_number; v->file_size = tmp->data_size; v->fnrs = file_numbers(files, &tmp->files); v->f = tmp->f; v->filename = unicode_copy(tmp->filename); tmp->f = 0; /*\ Insert in alphabetically correct place \*/ for (vv = volumes; *vv; vv = &((*vv)->next)) if (unicode_gt((*vv)->filename, v->filename)) break; v->next = *vv; *vv = v; } free_par(tmp); } } /*\ |*| Find all volumes in the current directory. \*/ par_t * find_all_par_files(void) { par_t *par, *tmp; pfile_t *v; hfile_t *p; par = create_par_header(0, 0); fprintf(stderr, "Looking for PAR archives:\n"); hash_directory("."); for (p = hfile; p; p = p->next) { if ((p->hashed >= HASH16K) && !IS_PAR(*p) && !is_old_par(&p->magic)) continue; tmp = read_par_header(p->filename, 0, 0, 1); if (!tmp) continue; if (tmp->vol_number) { if (!par_control_check(tmp)) { fprintf(stderr, " %-40s - CORRUPT\n", basename(p->filename)); free_par(tmp); continue; } CNEW(v, 1); v->match = p; v->vol_number = tmp->vol_number; v->file_size = tmp->data_size; v->fnrs = file_numbers(&par->files, &tmp->files); v->f = tmp->f; tmp->f = 0; v->next = par->volumes; par->volumes = v; fprintf(stderr, " %-40s - FOUND\n", basename(p->filename)); } free_par(tmp); } if (!par->volumes) { free_par(par); return 0; } return par; } /*\ Calc sub pattern for a sub string, Return result with rhs tacked on. \*/ static sub_t * make_sub_r(u16 *from, int fl, u16 *to, int tl, int off, sub_t *rhs) { int i, j, k, m; int ml, mf, mt; sub_t *ret; /*\ Cut off matching front and rear \*/ while ((*from == *to) && (fl > 0) && (tl > 0)) { from++; to++; fl--; tl--; off++; } while ((from[fl-1] == to[tl-1]) && (fl > 0) && (tl > 0)) { fl--; tl--; } /*\ Complete match \*/ if ((fl == 0) && (tl == 0)) return rhs; /*\ Look for longest match \*/ ml = 0, mf = 0, mt = 0; for (i = 0; (fl - i) > ml; i++) { for (j = 0; (tl - j) > ml; j++) { m = (fl - i); if (m > (tl - j)) m = (tl - j); for (k = 0; k < m; k++) if (from[i + k] != to[j + k]) break; if (k > ml) { ml = k; mf = i; mt = j; } } } /*\ No match found \*/ if (ml == 0) { NEW(ret, 1); ret->next = rhs; ret->off = off; ret->fs = from; ret->fl = fl; ret->ts = to; ret->tl = tl; return ret; } /*\ Match found: recurse with left and right parts \*/ ret = make_sub_r(from + mf + ml, fl - (mf + ml), to + mt + ml, tl - (mt + ml), off + mf + ml, rhs); return make_sub_r(from, mf, to, mt, off, ret); } /*\ |*| Calculate a substitution pattern \*/ sub_t * make_sub(u16 *from, u16 *to) { int fl, tl; for (fl = 0; from[fl]; fl++) ; for (tl = 0; to[tl]; tl++) ; return make_sub_r(from, fl, to, tl, 0, 0); } /*\ |*| Free s sub pattern. \*/ void free_sub(sub_t *sub) { sub_t *t; while (sub) { t = sub; sub = sub->next; free(t); } } /*\ |*| Pass the string through the substitution pattern \*/ u16 * do_sub(u16 *from, sub_t *sub) { static u16 *ret = 0; static int retl = 0; int i, fp, rp; sub_t *p; for (i = 0; from[i]; i++) ; for (p = sub; p; p = p->next) i += (p->tl - p->fl); if (retl < i+1) { retl = i+1; RENEW(ret, retl); } fp = rp = 0; for (p = sub; p; p = p->next) { while (fp < p->off) { if (!from[fp]) return from; ret[rp++] = from[fp++]; } for (i = 0; i < p->fl; i++) { if (from[fp++] != p->fs[i]) return from; } for (i = 0; i < p->tl; i++) ret[rp++] = p->ts[i]; } while (from[fp]) ret[rp++] = from[fp++]; ret[rp] = 0; return ret; } /*\ |*| Look for the sub pattern that makes most of the filenames match. |*| If less than m files match any pattern, 0 is returned. \*/ sub_t * find_best_sub(pfile_t *files, int m) { sub_t *best, *cur; pfile_t *p, *q; u16 *str; int i; best = 0; for (p = files; p; p = p->next) { if (!find_file(p, 0)) continue; cur = make_sub(p->filename, p->match->filename); i = 0; for (q = files; q; q = q->next) { if (!find_file(q, 0)) continue; if (!unicode_cmp(do_sub(q->filename, cur), q->match->filename)) i++; } if (i > m) { free_sub(best); best = cur; } else { free_sub(cur); } } return best; } par-cmdline/backend.h0100644000076500007650000000233607375537444014416 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| Backend operations \*/ #ifndef BACKEND_H #define BACKEND_H #include "types.h" #include "par.h" struct sub_s { sub_t *next; int off; u16 *fs; int fl; u16 *ts; int tl; }; char * basename(u16 *path); int unicode_cmp(u16 *a, u16 *b); int unicode_gt(u16 *a, u16 *b); hfile_t * hfile_add(u16 *filename); void hash_directory(char *dir); int hash_file(hfile_t *file, char type); int find_file(pfile_t *file, int displ); hfile_t * find_file_name(u16 *path, int displ); hfile_t * find_volume(u16 *name, i64 vol); int move_away(u16 *file, const u8 *ext); int rename_away(u16 *src, u16 *dst); u16 * file_numbers(pfile_t **list, pfile_t **files); int find_volumes(par_t *par, int tofind); void find_par_files(pfile_t **volumes, pfile_t **files, int part); par_t * find_all_par_files(void); int par_control_check(par_t *par); sub_t * make_sub(u16 *from, u16 *to); void free_sub(sub_t *sub); u16 * do_sub(u16 *from, sub_t *sub); sub_t * find_best_sub(pfile_t *files, int m); #endif /* BACKEND_H */ par-cmdline/ui_cli.c0100644000076500007650000001036507375722643014264 0ustar willemwillem/*\ |*| Parity Archive - A way to restore missing files in a set. |*| |*| Copyright (C) 2001 Willem Monsuwe (willem@stack.nl) |*| |*| File format by Stefan Wehlus - |*| initial idea by Tobias Rieper, further suggestions by Kilroy Balore |*| |*| CommandLine User Interface \*/ #include #include "interface.h" /*\ |*| Print usage \*/ static int usage(void) { printf( "Usage:\n" " par c(heck) [options] : Check parity archive\n" " par r(ecover) [options] : Restore missing volumes\n" " par a(dd) [options] [files] : Add files to parity archive\n" " Advanced:\n" " par m(ix) [options] : Try to restore from all parity files at once\n" " par i(nteractive) [] : Interactive mode (very bare-bones)\n" "\n" "Options: (Can be turned off with '+')\n" " -m : Move existing files out of the way\n" " -f : Fix faulty filenames\n" " -p: Number of files per parity volume\n" " or -n: Number of parity volumes to create\n" " -k : Keep broken files\n" " -s : Be smart if filenames are consistently different.\n" " +i : Do not add following files to parity volumes\n" " +c : Do not create parity volumes\n" " +C : Ignore case in filename comparisons\n" " +H : Do not check control hashes\n" " -v,+v: Increase or decrease verbosity\n" " -h,-?: Display this help\n" " -- : Always treat following arguments as files\n" "\n" ); return 0; } int ui_cli(int argc, char *argv[]) { int nvolumes = 10, pervol = 1, adding = 1, makepxx = 1, smart = 0, dash = 0, plus = 0; int (* flag)(int) = par_setflags; par_setflags(PARFLAG_CTRL | PARFLAG_CASE | PARFLAG_MOVE); if (argc == 1) return usage(); for (; argc > 1; argc--, argv++) { if (((argv[1][0] == '-') || (argv[1][0] == '+')) && argv[1][1] && !dash) { for (p = argv[1]; *p; p++) switch (*p) { case '-': if (p[1] == '-') { dash = 1; } else { plus = 1; flag = par_setflags; } break; case '+': plus = 0; flag = par_unsetflags; break; case 'm': flag(PARFLAG_MOVE); break; case 'i': cmd.add = plus; break; case 'f': cmd.fix = plus; break; case 'c': cmd.pxx = plus; break; case 'v': if (plus) cmd.loglevel++; else cmd.loglevel--; break; case 'p': case 'n': pervol = (*p == 'p'); while (isspace(*++p)) ; if (!*p) { argv++; argc--; p = argv[1]; } if (!isdigit(*p)) { fprintf(stderr, "Value expected!\n"); } else { cmd.volumes = 0; do { cmd.volumes *= 10; cmd.volumes += *p - '0'; } while (isdigit(*++p)); p--; } break; case 'H': flag(PARFLAG_CTRL); break; case 'C': flag(PARFLAG_CASE); break; case 'k': flag(PARFLAG_KEEP); break; case 's': smart = plus; break; case '?': case 'h': return usage(); default: fprintf(stderr, "Unknown switch: '%c'\n", *p); break; } continue; } if (!cmd.action) { switch (argv[1][0]) { case 'c': case 'C': cmd.action = ACTION_CHECK; break; case 'm': case 'M': cmd.action = ACTION_MIX; break; case 'r': case 'R': cmd.action = ACTION_RESTORE; break; case 'a': case 'A': cmd.action = ACTION_ADD; break; case 'i': case 'I': cmd.action = ACTION_TEXT_UI; break; default: fprintf(stderr, "Unknown command: '%s'\n", argv[1]); break; } continue; } switch (cmd.action) { default: cmd.action = ACTION_CHECK; /*\ FALLTHROUGH \*/ case ACTION_CHECK: case ACTION_RESTORE: fprintf(stderr, "Checking %s\n", argv[1]); par = read_par_header(unist(argv[1]), 0, 0, 0); if (!par) { fail = 2; continue; } if (check_par(par) < 0) fail = 1; free_par(par); par = 0; break; case ACTION_ADD: fprintf(stderr, "Adding to %s\n", argv[1]); par = read_par_header(unist(argv[1]), 1, 0, 0); if (!par) return 2; cmd.action = ACTION_ADDING; break; case ACTION_ADDING: par_add_file(par, find_file_name(unist(argv[1]), 1)); break; case ACTION_MIX: fprintf(stderr, "Unknown argument: '%s'\n", argv[1]); break; case ACTION_TEXT_UI: par_load(unist(argv[1])); break; } } }