diff options
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | LICENSE | 340 | ||||
-rw-r--r-- | Makefile | 28 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | conf-update.1 | 39 | ||||
-rw-r--r-- | conf-update.c | 438 | ||||
-rw-r--r-- | conf-update.conf | 31 | ||||
-rw-r--r-- | conf-update.h | 39 | ||||
-rw-r--r-- | config.c | 50 | ||||
-rw-r--r-- | config.h | 13 | ||||
-rw-r--r-- | core.c | 173 | ||||
-rw-r--r-- | core.h | 9 | ||||
-rw-r--r-- | helpers.c | 461 | ||||
-rw-r--r-- | helpers.h | 24 | ||||
-rw-r--r-- | index.c | 58 | ||||
-rw-r--r-- | index.h | 3 | ||||
-rw-r--r-- | modified.c | 94 | ||||
-rw-r--r-- | modified.h | 5 |
19 files changed, 1810 insertions, 0 deletions
@@ -0,0 +1,3 @@ +This is a list of people who contributed code to conf-update: + +Simon Stelling <blubb@gentoo.org> diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..505afd8 --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +* 2006-08-xx: Initial release @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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. + + <signature of Ty Coon>, 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5ed48fa --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +FLAGS = $$(pkg-config --cflags glib-2.0) -W -Wall -Wno-pointer-sign -g $(CFLAGS) +CC = gcc + +all: conf-update + +config.o: config.c conf-update.h + $(CC) $(FLAGS) -c config.c +core.o: core.c conf-update.h + $(CC) $(FLAGS) -c core.c +helpers.o: helpers.c conf-update.h + $(CC) $(FLAGS) -c helpers.c +conf-update.o: conf-update.c conf-update.h + $(CC) $(FLAGS) -c conf-update.c +index.o: index.c conf-update.h + $(CC) $(FLAGS) -c index.c +modified.o: conf-update.h modified.c + $(CC) $(FLAGS) -c modified.c + +conf-update.h: core.h helpers.h index.h modified.h config.h + +conf-update: core.o helpers.o conf-update.o index.o modified.o config.o + $(CC) $$(pkg-config --libs glib-2.0) -lncurses -lmenu -lcrypto $(LDFLAGS) -o conf-update config.o core.o helpers.o conf-update.o index.o modified.o + +.PHONY: clean + +clean: + rm -f *.o + rm -f conf-update @@ -0,0 +1 @@ +* Make it work on non-GNU systems diff --git a/conf-update.1 b/conf-update.1 new file mode 100644 index 0000000..7b79cd8 --- /dev/null +++ b/conf-update.1 @@ -0,0 +1,39 @@ +.TH "CONF-UPDATE" "1" "SEPTEMBER 2006" "conf-update" +.SH NAME +conf-update \- handle configuration file updates +.SH SYNOPSIS +\fBconf-update\fR [\fIpath1\fR] [\fIpath2\fR] [\fIpathN\fR] +.SH DESCRIPTION +.I conf-update +is supposed to be run after merging a new package to see if +there are updates to the configuration files. If a new +configuration file will override an old one, +.I conf-update +will prompt the user for a decision. +.PP +If given one or more paths, \fIconf-update\fR will search them for config files +to update. If no arguments are given, +.I conf-update +will check all directories in the \fICONFIG_PROTECT\fR variable. All +config files found in \fICONFIG_PROTECT_MASK\fR will automatically be +updated for you by \fIconf-update\fR. +.PP +.I conf-update +supports the following actions: \fIreplacing\fR, \fIdeleting\fR, \fImerging\fR +interactively. To select a directory and all its config files, select it +and press the space bar. +.SH OPTIONS +.TP +See \fB/etc/conf-update.conf\fR. +.SH "REPORTING BUGS" +Please report bugs via http://bugs.gentoo.org/ +.SH AUTHORS +.nf +Simon Stelling <blubb@gentoo.org> +.fi +.SH "FILES" +.TP +.B /etc/conf-update.conf +Configuration settings for \fIconf-update\fR are stored here. +.SH "SEE ALSO" +.BR make.conf (5) diff --git a/conf-update.c b/conf-update.c new file mode 100644 index 0000000..f987501 --- /dev/null +++ b/conf-update.c @@ -0,0 +1,438 @@ +#include "conf-update.h" + +int main(int argc, char **argv) { + bool cont, menu_changed, firstrun, doit; + bool *tmp_index; + char *config_protect, *config_protect_mask,*cmd, *myfile, *highest; + char *esc_highest, *esc_myfile; + char **result, **envvars, **protected, **masked, **md5_cache; + char **md5sum_cache, **myupdate, **merged_updates_report = NULL; + char **removed_updates_report = NULL; + const char *name, *myname; + int indent, myindent, i, j, file_count, c, item_ct, cur; + int merged_updates_ct = 0, removed_updates_ct = 0, arglen; + ITEM **items_list; + MENU *mymenu; + WINDOW *inner, *menu_win; + + read_config(); + sanity_checks(); + + if (argc == 1) { + fprintf(stderr, ">>> Getting CONFIG_PROTECT* variables from portage...\n"); + #ifdef DEBUG + // sandboxing is useful for debugging, believe me + envvars = get_listing("portageq envvar CONFIG_PROTECT CONFIG_PROTECT_MASK | sed -e \"s:^/:${SANDBOX}/:\" -e \"s: /: ${SANDBOX}/:g\"", "\n"); + #else + envvars = get_listing("portageq envvar CONFIG_PROTECT CONFIG_PROTECT_MASK", "\n"); + #endif + + if (is_valid_entry(envvars[0]) && is_valid_entry(envvars[1])) { + config_protect = strdup(envvars[0]); + config_protect_mask = strdup(envvars[1]); + free(envvars[0]); + free(envvars[1]); + free(envvars); + } else { + fprintf(stderr, "!!! failed. Aborting.\n"); + exit(EXIT_FAILURE); + } + + fprintf(stderr, ">>> Automerging updates in CONFIG_PROTECT_MASK...\n"); + masked = find_updates(config_protect_mask); + free(config_protect_mask); + for (i=0;!is_last_entry(masked[i]);i++) { + if (is_valid_entry(masked[i])) { + merged_updates_ct++; + merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *)); + merged_updates_report[merged_updates_ct-1] = get_real_filename(masked[i]); + merge(get_highest_update(masked, masked[i]), masked); + } + } + free(masked); + fprintf(stderr, ">>> Searching for updates in CONFIG_PROTECT...\n"); + } else { + if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + display_help(); + } else { + arglen = 0; + for (i=1;i<argc;i++) { + arglen += strlen(argv[i]) + 1; + } + config_protect = (char *)calloc(sizeof(char), arglen); + for (i=1;i<argc;i++) { + if (argv[i][0] != '/') { + // relative paths screw our indentation. maybe this should be a TODO? + fprintf(stderr, "!!! ERROR: Non-absolute path given as argument\n"); + exit(EXIT_FAILURE); + } + strcat(config_protect, argv[i]); + if (i < argc-1) { + strcat(config_protect, " "); + } + } + fprintf(stderr, ">>> Searching for updates in specified directories...\n"); + } + } + + protected = find_updates(config_protect); + + // it's important that we do this first + if (config.automerge_unmodified) { + fprintf(stderr, ">>> Automerging unmodified files...\n"); + file_count = 1; + md5_cache = (char **) malloc(sizeof(char *) * file_count); + md5sum_cache = (char **) malloc(sizeof(char *) * file_count); + md5_cache[0] = LAST_ENTRY; + md5sum_cache[0] = LAST_ENTRY; + for (i=0;!is_last_entry(protected[i]);i++) { + if (is_valid_entry(protected[i])) { + highest = get_highest_update(protected, protected[i]); + if (!strcmp(protected[i], highest)) { + md5_cache = (char **) realloc(md5_cache, sizeof(char *) * (file_count + 1)); + md5sum_cache = (char **) realloc(md5sum_cache, sizeof(char *) * (file_count + 1)); + md5_cache[file_count-1] = strdup(highest); + md5sum_cache[file_count-1] = (char *)malloc(sizeof(char) * 32); + calc_md5(md5_cache[file_count-1], md5sum_cache[file_count-1]); + md5_cache[file_count] = LAST_ENTRY; + md5sum_cache[file_count] = LAST_ENTRY; + file_count++; + } + } + } + for (i=0;!is_last_entry(protected[i]);i++) { + if (is_valid_entry(protected[i])) { + myfile = get_real_filename(protected[i]); + + if (!user_modified(myfile)) { + merged_updates_ct++; + merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *)); + merged_updates_report[merged_updates_ct-1] = get_real_filename(protected[i]); + merge(get_highest_update(protected, protected[i]), protected); + } + + free(myfile); + } + } + for (i=0;!is_last_entry(md5_cache[i]);i++) { + myfile = get_real_filename(md5_cache[i]); + md5sum_update(myfile, md5sum_cache[i]); + free(myfile); + free(md5_cache[i]); + free(md5sum_cache[i]); + } + free(md5_cache); + free(md5sum_cache); + } + + if (config.automerge_trivial) { + fprintf(stderr, ">>> Automerging trivial changes...\n"); + for (i=0;!is_last_entry(protected[i]);i++) { + if (is_valid_entry(protected[i])) { + myfile = get_real_filename(protected[i]); + esc_myfile = g_shell_quote(myfile); + highest = get_highest_update(protected, protected[i]); + esc_highest = g_shell_quote(highest); + cmd = (char *)calloc(strlen("diff -Nu % % | grep \"^[+-][^+-]\" | grep -v \"^[-+]#\" | grep -v \"^[-+][:space:]*$\" " ) + strlen(esc_highest) + strlen(esc_myfile), sizeof(char)); + strcpy(cmd, "diff -Nu "); + strcat(cmd, esc_myfile); + strcat(cmd, " "); + strcat(cmd, esc_highest); + strcat(cmd, " | grep \"^[+-][^+-]\" | grep -v \"^[-+]#\" | grep -v \"^[-+][:space:]*$\""); + + free(myfile); + free(esc_myfile); + free(esc_highest); + + result = get_listing(cmd, "\n"); + free(cmd); + if (is_last_entry(result[0])) { + merged_updates_ct++; + merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *)); + merged_updates_report[merged_updates_ct-1] = get_real_filename(highest); + merge(highest, protected); + } + for (j=0;!is_last_entry(result[j]);j++) { + free(result[j]); + } + free(result); + } + } + } + /***/ + // ncurses n'stuff + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + start_color(); + init_pair(1, COLOR_CYAN, COLOR_BLUE); + init_pair(2, COLOR_WHITE, COLOR_WHITE); + init_pair(3, COLOR_BLACK, COLOR_WHITE); + init_pair(4, COLOR_RED, COLOR_WHITE); + init_pair(5, COLOR_WHITE, COLOR_BLACK); + + draw_background(); + + inner = newwin(LINES - 4, COLS - 4, 2, 2); + keypad(inner, TRUE); + + draw_legend(inner); + + menu_win = subwin(inner, LINES - 7 - 6, COLS - 4 - 3, 8, 3); + + mymenu = create_menu(protected); + items_list = menu_items(mymenu); + set_menu_win(mymenu, inner); + set_menu_sub(mymenu, menu_win); + + post_menu(mymenu); + touchwin(inner); + wrefresh(inner); + menu_changed = FALSE; + while ((item_count(mymenu) > 1) && (c = wgetch(inner)) != 'q' && c != 'Q') { + switch(c) { + // navigation 1up/down + case KEY_DOWN: + menu_driver(mymenu, REQ_DOWN_ITEM); + break; + case KEY_UP: + menu_driver(mymenu, REQ_UP_ITEM); + break; + //navigation 1 page up/down + case KEY_PPAGE: + menu_driver(mymenu, REQ_SCR_UPAGE); + break; + case KEY_NPAGE: + menu_driver(mymenu, REQ_SCR_DPAGE); + break; + // navigation top/bottom + case KEY_HOME: + menu_driver(mymenu, REQ_FIRST_ITEM); + break; + case KEY_END: + menu_driver(mymenu, REQ_LAST_ITEM); + break; + + // select single + case ' ': + if ((strrchr(item_name(current_item(mymenu)), '/'))) { + // it's a dir, select all subdirs + files + name = item_name(current_item(mymenu)); + indent = 0; + while (name[indent] == INDENT_CHAR) { + indent++; + } + cont = TRUE; + while (cont) { + menu_driver(mymenu, REQ_DOWN_ITEM); + myname = item_name(current_item(mymenu)); + myindent = 0; + while (myname[myindent] == INDENT_CHAR) { + myindent++; + } + if (myindent > indent) { + if ((!strrchr(myname, '/'))) { + set_item_value(current_item(mymenu), TRUE); + } + } else { + menu_driver(mymenu, REQ_UP_ITEM); + cont = FALSE; + } + } + } else { + menu_driver(mymenu, REQ_TOGGLE_ITEM); + } + break; + // select all + case 'a': + case 'A': + menu_driver(mymenu, REQ_LAST_ITEM); + for (i=0;i<item_count(mymenu);i++) { + if ((!strrchr(item_name(current_item(mymenu)), '/'))) { + set_item_value(current_item(mymenu), TRUE); + } + menu_driver(mymenu, REQ_UP_ITEM); + } + menu_driver(mymenu, REQ_FIRST_ITEM); + break; + // deselect all + case 'u': + case 'U': + menu_driver(mymenu, REQ_LAST_ITEM); + for (i=0;i<item_count(mymenu);i++) { + menu_driver(mymenu, REQ_UP_ITEM); + set_item_value(current_item(mymenu), FALSE); + } + menu_driver(mymenu, REQ_FIRST_ITEM); + break; + // disp diff + case '\n': + case KEY_ENTER: + if (item_userptr(current_item(mymenu))) { + endwin(); + show_diff(*((char **)item_userptr(current_item(mymenu)))); + reset_prog_mode(); + } + break; + // edit update + case 'e': + case 'E': + if (item_userptr(current_item(mymenu))) { + endwin(); + edit_update(*((char **)item_userptr(current_item(mymenu)))); + reset_prog_mode(); + } + break; + // merge interactively + case 'm': + case 'M': + if (item_userptr(current_item(mymenu))) { + endwin(); + protected = merge_interactively(*((char **)item_userptr(current_item(mymenu))), protected); + reset_prog_mode(); + menu_changed = TRUE; + } + break; + + // merge/replace update + case 'r': + case 'R': + /* it is important that we go from last to first: + * if e.g. both 0000 and 0001 are selected for merging, this + * assures (given a sorted list), that 0001 gets merged before + * 0000 and therefore 0000 gets removed + */ + firstrun = config.check_actions; + doit = TRUE; + for (i=item_count(mymenu)-1;i>=0;i--) { + if (item_value(items_list[i]) == TRUE || (current_item(mymenu) == items_list[i] && item_userptr(items_list[i]))) { + if (firstrun) { + doit = get_confirmation(inner, "replace"); + firstrun = false; + } + if (doit) { + myupdate = (char **)item_userptr(items_list[i]); + if (is_valid_entry(*myupdate)) { + merged_updates_ct++; + merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *)); + merged_updates_report[merged_updates_ct-1] = get_real_filename(*myupdate); + menu_changed = TRUE; + merge(*myupdate, protected); + } + } + } + } + break; + // delete update + case 'd': + case 'D': + firstrun = config.check_actions; + doit = TRUE; + for (i=0;i<item_count(mymenu);i++) { + if (item_value(items_list[i]) == TRUE || (current_item(mymenu) == items_list[i] && item_userptr(items_list[i]))) { + if (firstrun) { + doit = get_confirmation(inner, "delete"); + firstrun = false; + } + if (doit) { + myupdate = (char **)item_userptr(items_list[i]); + exit_error(!unlink(*(myupdate)), *(myupdate)); + removed_updates_ct++; + removed_updates_report = (char**)realloc(removed_updates_report, removed_updates_ct * sizeof(char *)); + removed_updates_report[removed_updates_ct-1] = get_real_filename(*myupdate); + free(*myupdate); + *myupdate = SKIP_ENTRY; + menu_changed = TRUE; + } + } + } + break; + case KEY_RESIZE: + if (LINES > 13 && COLS > 55) { + // we don't want to loose the selection just because of a window resize + item_ct = item_count(mymenu); + cur = item_index(current_item(mymenu)); + tmp_index = malloc(sizeof(bool) * item_ct); + for (i=0;i<item_ct;i++) { + if (item_value(items_list[i]) == TRUE || (cur == i && item_userptr(items_list[i]))) { + tmp_index[i] = TRUE; + } else { + tmp_index[i] = FALSE; + } + } + remove_menu(mymenu); + delwin(menu_win); + delwin(inner); + draw_background(); + inner = newwin(LINES - 4, COLS - 4, 2, 2); + keypad(inner, TRUE); + draw_legend(inner); + menu_win = subwin(inner, LINES - 7 - 6, COLS - 4 - 5, 8, 5); + mymenu = create_menu(protected); + items_list = menu_items(mymenu); + set_menu_win(mymenu, inner); + set_menu_sub(mymenu, menu_win); + post_menu(mymenu); + + for (i=0;i<item_ct;i++) { + set_item_value(items_list[i], tmp_index[i]); + } + set_current_item(mymenu, items_list[cur]); + free(tmp_index); + } + break; + } + if (menu_changed) { + remove_menu(mymenu); + draw_legend(inner); + mymenu = create_menu(protected); + items_list = menu_items(mymenu); + set_menu_win(mymenu, inner); + set_menu_sub(mymenu, menu_win); + post_menu(mymenu); + menu_changed = FALSE; + } + touchwin(inner); + wrefresh(inner); + } + endwin(); + remove_menu(mymenu); + + if (merged_updates_ct > 0) { + fprintf(stdout, ">>> Merged updates for the following files:\n"); + for (i=0;i<merged_updates_ct;i++) { + fprintf(stdout, "\t%s\n", merged_updates_report[i]); + free(merged_updates_report[i]); + } + free(merged_updates_report); + } + if (removed_updates_ct > 0) { + fprintf(stdout, ">>> Deleted updates for the following files:\n"); + for (i=0;i<removed_updates_ct;i++) { + fprintf(stdout, "\t%s\n", removed_updates_report[i]); + free(removed_updates_report[i]); + } + free(removed_updates_report); + } + + for (i=0;!is_last_entry(protected[i]);i++) { + if (is_valid_entry(protected[i])) { + free(protected[i]); + } + } + free(protected); + if (config.pager) { + free(config.pager); + } + if (config.diff_tool) { + free(config.diff_tool); + } + if (config.merge_tool) { + free(config.merge_tool); + } + free(config.edit_tool); + free(config_protect); + fprintf(stderr, ">>> Nothing left to do... Bye!\n"); + exit(EXIT_SUCCESS); +} diff --git a/conf-update.conf b/conf-update.conf new file mode 100644 index 0000000..2dbd443 --- /dev/null +++ b/conf-update.conf @@ -0,0 +1,31 @@ +# conf-update configuration file using *.ini-style format +[conf-update] + +# If the update only affects comments, just apply it. +autoreplace_trivial=true + +# If the old configuration is the default one, just +# replace it with the new default. Very handy, but may +# result in a different behaviour when the package changes +# its default settings, therefore it is disabled here. +# Should be save for non-paranoid people, though. +autoreplace_unmodified=false + +# Whether to ask for confirmation before deleting/replacing a config +# If you're sane, you choose 'true' here. If you're lazy, you don't. +confirm_actions=true + +# Defines what tool to use to generate the diffs. I suggest one of +# the following: +diff_tool=diff -u +#diff_tool=colordiff -u +# YOU MUST COMMENT OUT "pager" BELOW, if you use this: +#diff_tool=vimdiff + +# Sets the pager to use. If you use vimdiff or don't want a pager at +# all, comment this out. +pager=less + +# Sets the tool used to merge config files interactively +# DO NOT USE VIMDIFF HERE, because the file name would be arbitrary +merge_tool=sdiff -s -o diff --git a/conf-update.h b/conf-update.h new file mode 100644 index 0000000..f3da09a --- /dev/null +++ b/conf-update.h @@ -0,0 +1,39 @@ +#ifndef CONF_UPDATE_H +#define CONF_UPDATE_H + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <string.h> +#include <errno.h> +#include <openssl/md5.h> +#include <curses.h> +#include <menu.h> +#include <dirent.h> +#include <glib.h> + +#define PROG_NAME "conf-update" +#define PROG_VERSION "$Rev: 4635 $" + +#define MD5SUM_INDEX "/var/lib/conf-update/md5sum_index" +#define MD5SUM_INDEX_DIR "/var/lib/conf-update/" +#define CONFIG_FILE "/etc/conf-update.conf" + +#define SKIP_ENTRY (char *)1 +#define LAST_ENTRY (char *)2 + +#define INDENT_CHAR ' ' +#define INDENT_STR " " + +#include "helpers.h" +#include "index.h" +#include "core.h" +#include "modified.h" +#include "config.h" + +#endif diff --git a/config.c b/config.c new file mode 100644 index 0000000..4637ed0 --- /dev/null +++ b/config.c @@ -0,0 +1,50 @@ +#include "conf-update.h" + +void read_config() { + extern struct configuration config; + GKeyFile *conffile; + GError *error = NULL; + + // set reasonable defaults + config.check_actions = TRUE; + + if (getenv("EDITOR")) { + config.edit_tool = strdup(getenv("EDITOR")); + } else { + fprintf(stderr, "!!! ERROR: environment variable EDITOR not set; edit your /etc/rc.conf\n" + "!!! If you are using sudo, make sure it doesn't clean out the env.\n"); + exit(EXIT_FAILURE); + } + + conffile = g_key_file_new(); + if (!g_key_file_load_from_file(conffile, CONFIG_FILE, G_KEY_FILE_NONE, NULL)) { + fprintf(stderr, "!!! ERROR: Could not load config file %s\n", CONFIG_FILE); + exit(EXIT_FAILURE); + } else { + config.automerge_trivial = g_key_file_get_boolean(conffile, PROG_NAME, "autoreplace_trivial", &error); + if (g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE) || g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + config.automerge_trivial = TRUE; + g_clear_error(&error); + } + config.automerge_unmodified = g_key_file_get_boolean(conffile, PROG_NAME, "autoreplace_unmodified", &error); + if (g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE) || g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + config.automerge_unmodified = FALSE; + g_clear_error(&error); + } + config.check_actions = g_key_file_get_boolean(conffile, PROG_NAME, "confirm_actions", &error); + if (g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE) || g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + config.check_actions = TRUE; + g_clear_error(&error); + } + if (!(config.diff_tool = g_key_file_get_string(conffile, PROG_NAME, "diff_tool", NULL))) { + config.diff_tool = strdup("diff -u"); + } + if (!(config.pager = g_key_file_get_string(conffile, PROG_NAME, "pager", NULL))) { + config.pager = strdup(""); + } + if (!(config.merge_tool = g_key_file_get_string(conffile, PROG_NAME, "merge_tool", NULL))) { + config.merge_tool = strdup("sdiff -s -o"); + } + } + g_key_file_free(conffile); +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..3271223 --- /dev/null +++ b/config.h @@ -0,0 +1,13 @@ +#define CONF_UPDATE_CONFIG_FILE "/etc/conf-update/conf-update.conf" + +struct configuration { + bool automerge_trivial; + bool automerge_unmodified; + bool check_actions; + char *pager; + char *diff_tool; + char *merge_tool; + char *edit_tool; +} config; + +void read_config(); @@ -0,0 +1,173 @@ +#include "conf-update.h" + +char *get_real_filename(const char *update) { + char *file = (char *)calloc(strlen(update) + 1 - strlen("._cfg????_"), sizeof(char)); + strncpy(file, update, strrchr(update, '/') - update + 1); + strcat(file, strrchr(update, '/') + strlen("._cfg????_") + 1); + + return file; +} + +char *get_highest_update(char **index, char *update) { + // update is just any update of the real file we want to get the highest update for + char *real_file = get_real_filename(update); + char *my_real_file; + char *highest_update = update; + int i; + + for (i=0;!is_last_entry(index[i]);i++) { + if (is_valid_entry(index[i])) { + my_real_file = get_real_filename(index[i]); + if (!strcmp(my_real_file, real_file)) { + if (strcmp(index[i], highest_update) > 0) { + highest_update = index[i]; + } + } + free(my_real_file); + } + } + + free(real_file); + + return highest_update; +} + +bool is_last_entry(const char *entry) { + if (entry == LAST_ENTRY) { + return TRUE; + } else { + return FALSE; + } +} + +bool is_valid_entry(const char *entry) { + if (entry == LAST_ENTRY || entry == SKIP_ENTRY) { + return FALSE; + } else { + return TRUE; + } +} + +void merge(char *update, char **index) { + char *real_file = get_real_filename(update); + char *my_real_file; + int i; + + exit_error(!rename(update, real_file), update); + + for (i=0;!is_last_entry(index[i]);i++) { + if (is_valid_entry(index[i])) { + my_real_file = get_real_filename(index[i]); + if (!strcmp(my_real_file, real_file)) { + if (strcmp(update, index[i])) { + exit_error(!unlink(index[i]), index[i]); + } + free(index[i]); + index[i] = SKIP_ENTRY; + } + free(my_real_file); + } + } + + free(real_file); +} + +void show_diff(char *update) { + extern struct configuration config; + char *realfile = get_real_filename(update); + char *esc_realfile = g_shell_quote(realfile); + char *esc_update = g_shell_quote(update); + char *cmd = (char *)calloc(strlen(config.diff_tool) + strlen(" % % | ") + strlen(esc_update) + strlen(esc_realfile) + strlen(config.pager) + 1, sizeof(char)); + strcpy(cmd, config.diff_tool); + strcat(cmd, " "); + strcat(cmd, esc_realfile); + strcat(cmd, " "); + strcat(cmd, esc_update); + if (strcmp(config.pager, "")) { + strcat(cmd, " | "); + strcat(cmd, config.pager); + } + free(realfile); + g_free(esc_realfile); + g_free(esc_update); + system(cmd); + free(cmd); +} + +void edit_update(char *update) { + extern struct configuration config; + char *esc_update = g_shell_quote(update); + char *cmd = calloc(strlen(config.edit_tool) + strlen(" ") + strlen(esc_update), sizeof(char)); + + strcpy(cmd, config.edit_tool); + strcat(cmd, " "); + strcat(cmd, esc_update); + + system(cmd); + g_free(esc_update); + free(cmd); +} + +char **merge_interactively(char *update, char **index) { + // customized versions are ._cfg????- with a minus instead of a underscore + // that way get_real_filename() works without modification + extern struct configuration config; + char *realfile = get_real_filename(update); + char *esc_realfile = g_shell_quote(realfile); + char *esc_update = g_shell_quote(update); + char *cmd = calloc(strlen("clear ; ") + strlen(config.merge_tool) + 2 * strlen(esc_update) + strlen(esc_realfile) + strlen(" % % %"), sizeof(char)); + char *merged, *esc_merged; + char **new_index = index; + int retval, ct; + + // interactively merge an interactively merged file? naah. + if (*(strstr(update, "._cfg") + strlen("._cfg????")) == '_') { + merged = strdup(update); + *(strstr(merged, "._cfg") + strlen("._cfg????")) = '-'; + esc_merged = g_shell_quote(merged); + strcpy(cmd, "clear ; "); + strcat(cmd, config.merge_tool); + strcat(cmd, " "); + strcat(cmd, esc_merged); + strcat(cmd, " "); + strcat(cmd, esc_realfile); + strcat(cmd, " "); + strcat(cmd, esc_update); + retval = WEXITSTATUS(system(cmd)); + + if (retval == 0 || retval == 1) { + for (ct=0;!is_last_entry(index[ct]);ct++) {} + new_index = realloc(index, (ct + 2) * sizeof(char *)); + new_index[ct] = strdup(merged); + new_index[ct+1] = LAST_ENTRY; + } else { + // user aborted or error + unlink(merged); + } + free(merged); + g_free(esc_merged); + } + free(cmd); + free(realfile); + g_free(esc_realfile); + g_free(esc_update); + + return new_index; +} +void display_help() { + char *str = \ + "Usage: " PROG_NAME " [options] [location1] [locationN] ...\n\n" + + "Options:\n" + "\t--help (-h) Show this message\n" + "\n" + "Locations: absolute path to a directory to search through\n" + " instead of CONFIG_PROTECT\n" + "\n" + "Shortcuts: \n" + "\tSelecting a directory will select all its updates\n"; + + + fprintf(stderr, str); + exit(EXIT_SUCCESS); +} @@ -0,0 +1,9 @@ +char *get_real_filename(const char *update); +char *get_highest_update(char **index, char *update); +bool is_last_entry(const char *entry); +bool is_valid_entry(const char *entry); +void merge(char *update, char **index); +void show_diff(char *update); +void edit_update(char *update); +char **merge_interactively(char *update, char **index); +void display_help(); diff --git a/helpers.c b/helpers.c new file mode 100644 index 0000000..a56e65d --- /dev/null +++ b/helpers.c @@ -0,0 +1,461 @@ +#include "conf-update.h" + +char **get_listing(char *cmd, char *delim) { + FILE *pipe; + char *buf = NULL; + size_t bufsize = 0; + ssize_t read; + char **listing; + int count = 1, i = 0; + unsigned int j; + char c; + + pipe = popen(cmd, "r"); + if (pipe) { + read = getdelim(&buf, &bufsize, '\0', pipe); + char *buf_backup = buf; + if (read != -1) { + // determine number of tokens + while ((c = buf[i]) != '\0') { + for (j=0;j<strlen(delim);j++) { + if (c == delim[j]) { + count++; + } + } + i++; + } + + listing = (char **) malloc(sizeof(char *) * count); + char *str; + i=0; + while ((str = strsep(&buf, delim))) { + listing[i] = strdup(str); + i++; + } + free(buf_backup); + // make sure the last one is always LAST_ENTRY + listing[count-1] = LAST_ENTRY; + pclose(pipe); + return listing; + } else { + pclose(pipe); + listing = (char **)malloc(sizeof(char *)); + listing[0] = LAST_ENTRY; + free(buf_backup); + return listing; + } + } else { + exit_error(0, cmd); + } + // just for gcc, that bitch + return NULL; +} + +int compare_updates(const void *a, const void *b) { + char *real_a; + char *real_b; + int result; + signed mod = 1; + + if (is_valid_entry(*(char **)a)) { + real_a = get_real_filename(*(char **)a); + } else { + real_a = NULL; + } + if (is_valid_entry(*(char **)b)) { + real_b = get_real_filename(*(char **)b); + } else { + real_b = NULL; + } + + if (!real_a && !real_b) { + result = -1; + } else if (!real_a) { + result = 1; + } else if (!real_b) { + result = -1; + } else { + // both valid updates + if ((result = strcmp(real_a, real_b)) == 0) { + // same target + if ((result = strncmp(strstr(*(char **)a, "._cfg"), strstr(*(char **)b, "._cfg"), strlen("._cfg????"))) == 0) { + // same update number. interactively merged vs. predefined, 1:0 for the user. + mod = -1; + } + result = mod * strcmp(*(char **)a, *(char **)b); + } + } + + free(real_a); + free(real_b); + + return result; +} + +struct node *fold_updates(char **list) { + struct node *root = malloc(sizeof(struct node)); + struct node *mynode, *newnode; + char *endtok, *curtok; + int i; + int run; + + root->name = strdup("/"); + root->children = malloc(sizeof(struct node *)); + root->ct_children = 0; + root->parent = NULL; + root->dir = TRUE; + root->link = NULL; + + for (i=0;!is_last_entry(list[i]);i++) { + if (is_valid_entry(list[i])) { + endtok = list[i]+1; + run = 1; + while (run) { + if (run == 2) { + // 2 means we're on the last run + run = 0; + endtok = list[i] + strlen(list[i]) + 1; + } else { + if ((endtok = strchr(endtok+1, '/')) == NULL) { + run = 2; + } + } + curtok = strndup(list[i], endtok - list[i]); + + mynode = find_node(root, curtok); + if (mynode == (struct node *)FALSE) { + mynode = root; + } + if (mynode != (struct node *)TRUE) { + // mynode is the parent of the new to be inserted node + newnode = malloc(sizeof(struct node)); + newnode->name = strdup(curtok); + if (!strcmp(curtok,list[i])) { + // it's the file + newnode->dir = FALSE; + newnode->link = &list[i]; + } else { + newnode->name = strdup(curtok); + newnode->dir = TRUE; + newnode->link = NULL; + } + newnode->children = malloc(sizeof(struct node *)); + newnode->ct_children = 0; + newnode->parent = mynode; + + mynode->ct_children++; + mynode->children = realloc(mynode->children, sizeof(struct node *) * mynode->ct_children); + mynode->children[mynode->ct_children-1] = newnode; + } + + free(curtok); + } + } + } + + return root; + +} + +struct node *find_node(struct node *root, char *path) { + int i; + struct node *mynode; + + if (!strcmp(root->name, path)) { + // already exists + return (struct node *)TRUE; + } else if (!strncmp(root->name, path, strlen(root->name)) && root->dir == TRUE) { + // at least it's in the same direction, go through the list of children + for (i=0;i<root->ct_children;i++) { + mynode = find_node(root->children[i], path); + if (mynode == (struct node *)TRUE) { + return (struct node *)TRUE; + } else if (mynode != (struct node *)FALSE) { + return mynode; + } + } + // if we hit this point, nothing was found, meaning that it has to be a child of the current node + return root; + } else { + // completely wrong + return (struct node *)FALSE; + } +} +void sanity_checks() { + extern struct configuration config; + unsigned int i; + FILE *pipe; + char *cmd = NULL; + char *tools[] = { + "diff", + "portageq", + strndup(config.pager, strchrnul(config.pager, ' ') - config.pager), + strndup(config.diff_tool, strchrnul(config.diff_tool, ' ') - config.diff_tool), + strndup(config.merge_tool, strchrnul(config.merge_tool, ' ') - config.merge_tool), + strndup(config.edit_tool, strchrnul(config.edit_tool, ' ') - config.edit_tool) + }; + + if (getuid() != 0) { + fprintf(stderr, "!!! Oops, you're not root!\n"); + exit(EXIT_FAILURE); + } + + for (i=0;i<sizeof(tools)/sizeof(tools[0]);i++) { + // "" is okay for pager + if (strcmp(tools[i], "")) { + if (!g_find_program_in_path((gchar *)tools[i])) { + fprintf(stderr, "!!! ERROR: couldn't find necesary tool: %s\n", tools[i]); + exit(EXIT_FAILURE); + } + } + } + free(cmd); + + mkdir (MD5SUM_INDEX_DIR, 0755); + if ((pipe = fopen(MD5SUM_INDEX, "a"))) { + fclose(pipe); + } else { + fprintf(stderr, "!!! ERROR: Can't write to %s; check permissions", MD5SUM_INDEX); + exit(EXIT_FAILURE); + } +} + +void draw_legend(WINDOW *inner) { + int i; + + wattron(inner, COLOR_PAIR(2)); + wattron(inner, A_BOLD); + box(inner, 0, 0); + for (i=1;i<LINES-5;i++) { + mvwhline(inner, i, 1, ' ', COLS-6); + } + + wattroff(inner, A_BOLD); + wattron(inner, COLOR_PAIR(3)); + mvwprintw(inner, 1, 2, "Select current: !!!!!!! | !!!ll | !!!nselect all"); + mvwprintw(inner, 2, 2, "Show diff: !!!!!!! | !!!dit update | !!!erge interactively"); + mvwprintw(inner, 3, 2, "Actions for all selected: !!!eplace config file(s) | !!!elete update(s)"); // | merge !!!nteractively"); + mvwprintw(inner, 4, 2, "Quit: !!!"); + + wattron(inner, COLOR_PAIR(4)); + mvwprintw(inner, 1, 2 + strlen("Select current: "), "[SPACE]"); + mvwprintw(inner, 1, 2 + strlen("Select current: !!!!!!! | "), "[A]"); + mvwprintw(inner, 1, 2 + strlen("Select current: !!!!!!! | !!!ll | "), "[U]"); + + mvwprintw(inner, 2, 2 + strlen("Show diff: "), "[ENTER]"); + mvwprintw(inner, 2, 2 + strlen("Show diff: !!!!!!! | "), "[E]"); + mvwprintw(inner, 2, 2 + strlen("Show diff: !!!!!!! | !!!dit update | "), "[M]"); + + mvwprintw(inner, 3, 2 + strlen("Actions for all selected: "), "[R]"); + mvwprintw(inner, 3, 2 + strlen("Actions for all selected: !!!eplace config file(s) | "), "[D]"); + //mvwprintw(inner, 3, 2 + strlen("Action shortcuts: !!!erge | !!!elete update | merge "), "[I]"); + + mvwprintw(inner, 4, 2 + strlen("Quit: "), "[Q]"); + + wattron(inner, COLOR_PAIR(2) | A_BOLD); + // TODO: replace COLS - 4/LINES - 7 with a function to determine size of inner + mvwhline(inner, 5, 1, 0, COLS - 4 -2); + mvwhline(inner, LINES - 7, 1, 0, COLS - 4 -2); + + wrefresh(inner); +} + +void draw_background() { + int i; + + attron(A_BOLD); + attron(COLOR_PAIR(1)); + // why does clear() not work here? + for (i=0;i<LINES;i++) { + mvhline(i, 0, ' ', COLS); + } + attron(COLOR_PAIR(5)); + mvhline(LINES-2, 3, ' ', COLS-4); + mvvline(3, COLS-2, ' ', LINES-4); + attron(COLOR_PAIR(1)); + mvprintw(0,1, PROG_NAME); + mvprintw(0,strlen(PROG_NAME) + 2, PROG_VERSION); + mvhline(1, 1, ACS_HLINE, COLS - 2); + + refresh(); +} + +char *get_indent_name(struct node *update, int width) { + int ct_indents = 0; + struct node *mynode = update; + char *start, *name; + char *indent_name; + char number[] = "0000"; + int num, remainder, i; + + while ((mynode = mynode->parent)) { + ct_indents++; + } + indent_name = calloc(width + 1, sizeof(char)); + if ((start = strstr(update->name, "._cfg"))) { + name = start+strlen("._cfg????_"); + while (ct_indents > 0) { + strcat(indent_name, INDENT_STR); + ct_indents--; + } + strcat(indent_name, name); + strcat(indent_name, " ("); + strncpy(number, start+strlen("._cfg"), 4); + num = atoi(number) + 1; + snprintf(indent_name + strlen(indent_name), 4, "%d", num); + strcat(indent_name, ")"); + if (*(name - 1) == '-') { + strcat(indent_name, "(merged)"); + } + } else { + start = strrchr(update->name, '/') + 1; + while (ct_indents > 0) { + strcat(indent_name, INDENT_STR); + ct_indents--; + } + strcat(indent_name, start); + strcat(indent_name, "/"); + } + remainder = width - strlen(indent_name); + for(i=0;i<remainder;i++) { + strcat(indent_name, " "); + } + + return indent_name; +} +int count_array_items(struct node *root) { + int count = 0, i; + + for (i=0;i<root->ct_children;i++) { + count += count_array_items(root->children[i]); + } + return 1 + count; +} + +void build_item_array(ITEM **item_array, struct node *root, int menu_width) { + int i = 0; + + // fast-forward to the next NULL entry + while (item_array[i]) { + i++; + } + item_array[i] = new_item(get_indent_name(root, menu_width), ""); + set_item_userptr(item_array[i], root->link); + + for (i=0;i<root->ct_children;i++) { + build_item_array(item_array, root->children[i], menu_width); + } +} +void free_folded(struct node *root) { + int i; + + for (i=0;i<root->ct_children;i++) { + free_folded(root->children[i]); + } + if (root->dir) { + free(root->name); + } + free(root->children); + free(root); +} + +bool get_confirmation(WINDOW *win, char *action) { + bool result; + echo(); + nocbreak(); + char ret[2] = " "; + wattron(win, COLOR_PAIR(4)); + wattroff(win, A_BOLD); + // TODO: replace COLS - 4/LINES - 7 with a function to determine size of inner + mvwprintw(win, LINES - 6, 2, "Do you really want to "); + wprintw(win, action); + wprintw(win, " all selected updates? [y/n] "); + refresh(); + wgetnstr(win, ret, 1); + if (!strcmp(ret,"y")) { + result = true; + } else { + result = false; + } + mvwhline(win, LINES - 6, 1, ' ', COLS - 4 -2); + noecho(); + cbreak(); + return result; +} +void exit_error(bool expr, char *hint) { + if (!expr) { + endwin(); + char *mystring = calloc(sizeof(char), strlen("!!! ERROR: ") + strlen(hint) + 1); + sprintf(mystring, "!!! ERROR: %s", hint); + perror(mystring); + exit(EXIT_FAILURE); + } +} + +char **get_files_list(char *searchpath, char **index, int *max) { + struct stat mystat, tmpstat; + int i = 0, j; + DIR *dirfd; + struct dirent *file = (struct dirent *)TRUE; + char *absfile; + char *myfile; + bool ignore; + + lstat(searchpath, &mystat); + if (S_ISDIR(mystat.st_mode)) { + dirfd = opendir(searchpath); + if (dirfd) { + while (file) { + file = readdir(dirfd); + if (file && strcmp(file->d_name, ".") && strcmp(file->d_name, "..") && \ + strcmp(file->d_name, ".svn") && strcmp(file->d_name, "CVS")) { + absfile = (char *)calloc((strlen(searchpath) + strlen("/") + strlen(file->d_name) + 1), sizeof(char *)); + strcpy(absfile, searchpath); + strcat(absfile, "/"); + strcat(absfile, file->d_name); + index = get_files_list(absfile, index, max); + free(absfile); + } + } + } else if (errno == ENOENT) { + return index; + } else { + exit_error(0, searchpath); + } + closedir(dirfd); + } else if (S_ISREG(mystat.st_mode)) { + if (!strncmp(strrchr(searchpath, '/')+1, "._cfg", strlen("._cfg"))) { + if (*(searchpath+strlen(searchpath)-1) != '~' && \ + strcmp(searchpath+strlen(searchpath)-4,".bak")) { + myfile = get_real_filename(searchpath); + if (access(myfile, F_OK) != 0) { + // we don't want phantom updates + unlink(searchpath); + } else { + // we don't want duplicates either + ignore = FALSE; + for (j=0;j<(*max);j++) { + lstat(index[j], &tmpstat); + if (tmpstat.st_dev == mystat.st_dev && \ + tmpstat.st_ino == mystat.st_ino) { + ignore = TRUE; + } + } + if (!ignore) { + while (i < (*max) && !is_last_entry(index[i])) { + i++; + } + if (i + 1 >= (*max)) { + (*max)++; + index = (char **)realloc(index, (*max) * sizeof(char *)); + } + index[i] = strdup(searchpath); + index[i+1] = LAST_ENTRY; + } + } + free(myfile); + } + } + } + return index; +} diff --git a/helpers.h b/helpers.h new file mode 100644 index 0000000..86f0819 --- /dev/null +++ b/helpers.h @@ -0,0 +1,24 @@ +char **get_listing(char *cmd, char *delim); +int compare_updates(const void *a, const void *b); +struct node *find_node(struct node *root, char *path); +void sanity_checks(); +void draw_legend(WINDOW *inner); +void draw_background(); +struct node *fold_updates(char **list); +struct node *find_node(struct node *root, char *path); +char *get_indent_name(struct node *update, int width); +void build_item_array(ITEM **item_array, struct node *root, int menu_width); +int count_array_items(struct node *root); +void free_folded(struct node *root); +bool get_confirmation(WINDOW *win, char *action); +void exit_error(bool expr, char *hint); +char **get_files_list(char *searchpath, char **index, int *max); + +struct node { + char *name; + struct node **children; + int ct_children; + struct node *parent; + bool dir; + char **link; +}; @@ -0,0 +1,58 @@ +#include "conf-update.h" + +char **find_updates(char *searchdir) { + int max = 2; + char **listing = (char **)malloc(sizeof(char *) * max); + listing[0] = LAST_ENTRY; + listing[1] = NULL; + + char *searchstr = strdup(searchdir); + char *srchstrbak = searchstr; + char *searchtok; + + while ((searchtok = strsep(&searchstr, " "))) { + listing = get_files_list(searchtok, listing, &max); + } + free(srchstrbak); + return listing; +} + +MENU *create_menu(char **protected) { + int i, arraycount = 0; + ITEM **item_array; + MENU *mymenu; + + for (i=0;!is_last_entry(protected[i]);i++) { + arraycount++; + } + qsort(protected, arraycount, sizeof(char *), compare_updates); + struct node *folded_protected = fold_updates(protected); + item_array = (ITEM **)calloc(count_array_items(folded_protected) + 1, sizeof(ITEM *)); + build_item_array(item_array, folded_protected, COLS - 10); + + mymenu = new_menu(item_array); + set_menu_mark(mymenu, " * "); + menu_opts_off(mymenu, O_ONEVALUE); + menu_opts_off(mymenu, O_NONCYCLIC); + set_menu_fore(mymenu, A_NORMAL); + set_menu_grey(mymenu, A_STANDOUT); + set_menu_back(mymenu, A_STANDOUT); + + free_folded(folded_protected); + set_menu_format(mymenu, LINES - 7 - 6, 1); + return mymenu; +} + +void remove_menu(MENU *mymenu) { + ITEM **item_list = menu_items(mymenu); + int i; + + unpost_menu(mymenu); + + for (i=0;i<item_count(mymenu);i++) { + free(item_name(item_list[i])); + free_item(item_list[i]); + } + free_menu(mymenu); + free(item_list); +} @@ -0,0 +1,3 @@ +char **find_updates(char *searchdir); +MENU *create_menu(char **protected); +void remove_menu(MENU *mymenu); diff --git a/modified.c b/modified.c new file mode 100644 index 0000000..81e8664 --- /dev/null +++ b/modified.c @@ -0,0 +1,94 @@ +#include "conf-update.h" + +bool user_modified(char *file) { + FILE *indexpipe, *filepipe; + char *line = NULL; + char *filedump = NULL; + size_t len = 0, len2 = 0; + char *md5sum; + char filemd5[MD5_DIGEST_LENGTH]; + char hexdigest[32]; + bool user_mod = TRUE; + if (access(MD5SUM_INDEX, R_OK) != 0) { + return TRUE; + } else { + indexpipe = fopen(MD5SUM_INDEX, "r"); + exit_error(indexpipe, MD5SUM_INDEX); + while (getline(&line, &len, indexpipe) != -1) { + if (!strncmp(line, file, strlen(file)) && *(line + strlen(file)) == ' ') { + md5sum = strrchr(line, ' ') + 1; + filepipe = fopen(file, "r"); + exit_error(filepipe, file); + if (getdelim(&filedump, &len2, EOF, filepipe) != -1) { + MD5(filedump, strlen(filedump), filemd5); + md52hex(filemd5, hexdigest); + if (!strncmp(md5sum, hexdigest, 32)) { + user_mod = FALSE; + } + } + fclose(filepipe); + free(filedump); + } + } + fclose(indexpipe); + free(line); + return user_mod; + } +} + +void md52hex(char *md5sum, char *hexdigest) { + // this one is stolen from python's md5module.c + char c; + int i, j = 0; + + for(i=0; i<16; i++) { + c = (md5sum[i] >> 4) & 0xf; + hexdigest[j] = (c>9) ? c+'a'-10 : c + '0'; + j++; + c = (md5sum[i] & 0xf); + hexdigest[j] = (c>9) ? c+'a'-10 : c + '0'; + j++; + } +} + +void calc_md5(char *file, char* hexdigest) { + FILE *fp; + char *dump = NULL; + size_t len = 0; + + char md5sum[MD5_DIGEST_LENGTH]; + + fp = fopen(file, "r"); + if (getdelim(&dump, &len, EOF, fp) != -1) { + MD5(dump, strlen(dump), md5sum); + md52hex(md5sum, hexdigest); + free(dump); + } + fclose(fp); +} + +void md5sum_update(char *file, char *hexdigest) { + FILE *oldpipe; + char *dump = NULL; + size_t len = 0; + char *entry; + oldpipe = fopen(MD5SUM_INDEX, "r"); + exit_error(oldpipe, MD5SUM_INDEX); + + getdelim(&dump, &len, EOF, oldpipe); + if ((entry = strstr(dump, file))) { + entry = strchr(entry, '\n') - 32; + strncpy(entry, hexdigest, 32); + exit_error(freopen(MD5SUM_INDEX, "w", oldpipe), MD5SUM_INDEX); + exit_error(fwrite(dump, strlen(dump), sizeof(char), oldpipe), MD5SUM_INDEX); + } else { + // there's no entry at all yet + exit_error(freopen(MD5SUM_INDEX, "a", oldpipe), MD5SUM_INDEX); + exit_error(fwrite(file, strlen(file), sizeof(char), oldpipe), MD5SUM_INDEX); + exit_error(fwrite(" ", strlen(" "), sizeof(char), oldpipe), MD5SUM_INDEX); + exit_error(fwrite(hexdigest, 32, sizeof(char), oldpipe), MD5SUM_INDEX); + exit_error(fwrite("\n", strlen("\n"), sizeof(char), oldpipe), MD5SUM_INDEX); + } + free(dump); + fclose(oldpipe); +} diff --git a/modified.h b/modified.h new file mode 100644 index 0000000..d99c46b --- /dev/null +++ b/modified.h @@ -0,0 +1,5 @@ +bool user_modified(char *file); +void md52hex(char *md5sum, char *hexdigest); +void calc_md5(char *file, char* hexdigest); +void md5sum_update_file(char *file, char *hexdigest); +void md5sum_update(char *file, char *hexdigest); |