aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS3
-rw-r--r--ChangeLog1
-rw-r--r--LICENSE340
-rw-r--r--Makefile28
-rw-r--r--TODO1
-rw-r--r--conf-update.139
-rw-r--r--conf-update.c438
-rw-r--r--conf-update.conf31
-rw-r--r--conf-update.h39
-rw-r--r--config.c50
-rw-r--r--config.h13
-rw-r--r--core.c173
-rw-r--r--core.h9
-rw-r--r--helpers.c461
-rw-r--r--helpers.h24
-rw-r--r--index.c58
-rw-r--r--index.h3
-rw-r--r--modified.c94
-rw-r--r--modified.h5
19 files changed, 1810 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..a6cb95d
--- /dev/null
+++ b/AUTHORS
@@ -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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..b9b1bad
--- /dev/null
+++ b/TODO
@@ -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();
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..de9cc64
--- /dev/null
+++ b/core.c
@@ -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);
+}
diff --git a/core.h b/core.h
new file mode 100644
index 0000000..e39a951
--- /dev/null
+++ b/core.h
@@ -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;
+};
diff --git a/index.c b/index.c
new file mode 100644
index 0000000..92bc4e4
--- /dev/null
+++ b/index.c
@@ -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);
+}
diff --git a/index.h b/index.h
new file mode 100644
index 0000000..ca6fed9
--- /dev/null
+++ b/index.h
@@ -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);