libalpm
Arch Linux Package Manager Library
conflict.c
Go to the documentation of this file.
00001 /*
00002  *  conflict.c
00003  *
00004  *  Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
00005  *  Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
00006  *  Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
00007  *  Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org>
00008  *  Copyright (c) 2006 by Miklos Vajna <vmiklos@frugalware.org>
00009  *  Copyright (c) 2006 by Christian Hamar <krics@linuxforum.hu>
00010  *
00011  *  This program is free software; you can redistribute it and/or modify
00012  *  it under the terms of the GNU General Public License as published by
00013  *  the Free Software Foundation; either version 2 of the License, or
00014  *  (at your option) any later version.
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU General Public License for more details.
00020  *
00021  *  You should have received a copy of the GNU General Public License
00022  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00023  */
00024 
00025 #include <stdlib.h>
00026 #include <stdio.h>
00027 #include <string.h>
00028 #include <limits.h>
00029 #include <sys/stat.h>
00030 #include <dirent.h>
00031 
00032 /* libalpm */
00033 #include "conflict.h"
00034 #include "alpm_list.h"
00035 #include "alpm.h"
00036 #include "handle.h"
00037 #include "trans.h"
00038 #include "util.h"
00039 #include "log.h"
00040 #include "deps.h"
00041 
00042 static alpm_conflict_t *conflict_new(alpm_pkg_t *pkg1, alpm_pkg_t *pkg2,
00043         alpm_depend_t *reason)
00044 {
00045     alpm_conflict_t *conflict;
00046 
00047     MALLOC(conflict, sizeof(alpm_conflict_t), return NULL);
00048 
00049     conflict->package1_hash = pkg1->name_hash;
00050     conflict->package2_hash = pkg2->name_hash;
00051     STRDUP(conflict->package1, pkg1->name, return NULL);
00052     STRDUP(conflict->package2, pkg2->name, return NULL);
00053     conflict->reason = reason;
00054 
00055     return conflict;
00056 }
00057 
00058 void _alpm_conflict_free(alpm_conflict_t *conflict)
00059 {
00060     FREE(conflict->package2);
00061     FREE(conflict->package1);
00062     FREE(conflict);
00063 }
00064 
00065 alpm_conflict_t *_alpm_conflict_dup(const alpm_conflict_t *conflict)
00066 {
00067     alpm_conflict_t *newconflict;
00068     CALLOC(newconflict, 1, sizeof(alpm_conflict_t), return NULL);
00069 
00070     newconflict->package1_hash = conflict->package1_hash;
00071     newconflict->package2_hash = conflict->package2_hash;
00072     STRDUP(newconflict->package1, conflict->package1, return NULL);
00073     STRDUP(newconflict->package2, conflict->package2, return NULL);
00074     newconflict->reason = conflict->reason;
00075 
00076     return newconflict;
00077 }
00078 
00079 static int conflict_isin(alpm_conflict_t *needle, alpm_list_t *haystack)
00080 {
00081     alpm_list_t *i;
00082     for(i = haystack; i; i = i->next) {
00083         alpm_conflict_t *conflict = i->data;
00084         if(needle->package1_hash == conflict->package1_hash
00085                 && needle->package2_hash == conflict->package2_hash
00086                 && strcmp(needle->package1, conflict->package1) == 0
00087                 && strcmp(needle->package2, conflict->package2) == 0) {
00088             return 1;
00089         }
00090     }
00091 
00092     return 0;
00093 }
00094 
00095 /** Adds the pkg1/pkg2 conflict to the baddeps list.
00096  * @param handle the context handle
00097  * @param baddeps list to add conflict to
00098  * @param pkg1 first package
00099  * @param pkg2 package causing conflict
00100  * @param reason reason for this conflict
00101  */
00102 static int add_conflict(alpm_handle_t *handle, alpm_list_t **baddeps,
00103         alpm_pkg_t *pkg1, alpm_pkg_t *pkg2, alpm_depend_t *reason)
00104 {
00105     alpm_conflict_t *conflict = conflict_new(pkg1, pkg2, reason);
00106     if(!conflict) {
00107         return -1;
00108     }
00109     if(!conflict_isin(conflict, *baddeps)) {
00110         char *conflict_str = alpm_dep_compute_string(reason);
00111         *baddeps = alpm_list_add(*baddeps, conflict);
00112         _alpm_log(handle, ALPM_LOG_DEBUG, "package %s conflicts with %s (by %s)\n",
00113                 pkg1->name, pkg2->name, conflict_str);
00114         free(conflict_str);
00115     } else {
00116         _alpm_conflict_free(conflict);
00117     }
00118     return 0;
00119 }
00120 
00121 /** Check if packages from list1 conflict with packages from list2.
00122  * This looks at the conflicts fields of all packages from list1, and sees
00123  * if they match packages from list2.
00124  * If a conflict (pkg1, pkg2) is found, it is added to the baddeps list
00125  * in this order if order >= 0, or reverse order (pkg2,pkg1) otherwise.
00126  *
00127  * @param handle the context handle
00128  * @param list1 first list of packages
00129  * @param list2 second list of packages
00130  * @param baddeps list to store conflicts
00131  * @param order if >= 0 the conflict order is preserved, if < 0 it's reversed
00132  */
00133 static void check_conflict(alpm_handle_t *handle,
00134         alpm_list_t *list1, alpm_list_t *list2,
00135         alpm_list_t **baddeps, int order) {
00136     alpm_list_t *i;
00137 
00138     if(!baddeps) {
00139         return;
00140     }
00141     for(i = list1; i; i = i->next) {
00142         alpm_pkg_t *pkg1 = i->data;
00143         alpm_list_t *j;
00144 
00145         for(j = alpm_pkg_get_conflicts(pkg1); j; j = j->next) {
00146             alpm_depend_t *conflict = j->data;
00147             alpm_list_t *k;
00148 
00149             for(k = list2; k; k = k->next) {
00150                 alpm_pkg_t *pkg2 = k->data;
00151 
00152                 if(pkg1->name_hash == pkg2->name_hash
00153                         && strcmp(pkg1->name, pkg2->name) == 0) {
00154                     /* skip the package we're currently processing */
00155                     continue;
00156                 }
00157 
00158                 if(_alpm_depcmp(pkg2, conflict)) {
00159                     if(order >= 0) {
00160                         add_conflict(handle, baddeps, pkg1, pkg2, conflict);
00161                     } else {
00162                         add_conflict(handle, baddeps, pkg2, pkg1, conflict);
00163                     }
00164                 }
00165             }
00166         }
00167     }
00168 }
00169 
00170 /* Check for inter-conflicts */
00171 alpm_list_t *_alpm_innerconflicts(alpm_handle_t *handle, alpm_list_t *packages)
00172 {
00173     alpm_list_t *baddeps = NULL;
00174 
00175     _alpm_log(handle, ALPM_LOG_DEBUG, "check targets vs targets\n");
00176     check_conflict(handle, packages, packages, &baddeps, 0);
00177 
00178     return baddeps;
00179 }
00180 
00181 /* Check for target vs (db - target) conflicts */
00182 alpm_list_t *_alpm_outerconflicts(alpm_db_t *db, alpm_list_t *packages)
00183 {
00184     alpm_list_t *baddeps = NULL;
00185 
00186     if(db == NULL) {
00187         return NULL;
00188     }
00189 
00190     alpm_list_t *dblist = alpm_list_diff(_alpm_db_get_pkgcache(db),
00191             packages, _alpm_pkg_cmp);
00192 
00193     /* two checks to be done here for conflicts */
00194     _alpm_log(db->handle, ALPM_LOG_DEBUG, "check targets vs db\n");
00195     check_conflict(db->handle, packages, dblist, &baddeps, 1);
00196     _alpm_log(db->handle, ALPM_LOG_DEBUG, "check db vs targets\n");
00197     check_conflict(db->handle, dblist, packages, &baddeps, -1);
00198 
00199     alpm_list_free(dblist);
00200     return baddeps;
00201 }
00202 
00203 /** Check the package conflicts in a database
00204  *
00205  * @param handle the context handle
00206  * @param pkglist the list of packages to check
00207  * @return an alpm_list_t of alpm_conflict_t
00208  */
00209 alpm_list_t SYMEXPORT *alpm_checkconflicts(alpm_handle_t *handle,
00210         alpm_list_t *pkglist)
00211 {
00212     CHECK_HANDLE(handle, return NULL);
00213     return _alpm_innerconflicts(handle, pkglist);
00214 }
00215 
00216 static const int DIFFERENCE = 0;
00217 static const int INTERSECT = 1;
00218 /* Returns a set operation on the provided two lists of files.
00219  * Pre-condition: both lists are sorted!
00220  * When done, free the list but NOT the contained data.
00221  *
00222  * Operations:
00223  *   DIFFERENCE - a difference operation is performed. filesA - filesB.
00224  *   INTERSECT - an intersection operation is performed. filesA & filesB.
00225  */
00226 static alpm_list_t *filelist_operation(alpm_filelist_t *filesA,
00227         alpm_filelist_t *filesB, int operation)
00228 {
00229     alpm_list_t *ret = NULL;
00230     size_t ctrA = 0, ctrB = 0;
00231 
00232     while(ctrA < filesA->count && ctrB < filesB->count) {
00233         alpm_file_t *fileA = filesA->files + ctrA;
00234         alpm_file_t *fileB = filesB->files + ctrB;
00235         const char *strA = fileA->name;
00236         const char *strB = fileB->name;
00237         /* skip directories, we don't care about them */
00238         if(strA[strlen(strA)-1] == '/') {
00239             ctrA++;
00240         } else if(strB[strlen(strB)-1] == '/') {
00241             ctrB++;
00242         } else {
00243             int cmp = strcmp(strA, strB);
00244             if(cmp < 0) {
00245                 if(operation == DIFFERENCE) {
00246                     /* item only in filesA, qualifies as a difference */
00247                     ret = alpm_list_add(ret, fileA);
00248                 }
00249                 ctrA++;
00250             } else if(cmp > 0) {
00251                 ctrB++;
00252             } else {
00253                 if(operation == INTERSECT) {
00254                     /* item in both, qualifies as an intersect */
00255                     ret = alpm_list_add(ret, fileA);
00256                 }
00257                 ctrA++;
00258                 ctrB++;
00259             }
00260       }
00261     }
00262 
00263     /* if doing a difference, ensure we have completely emptied pA */
00264     while(operation == DIFFERENCE && ctrA < filesA->count) {
00265         alpm_file_t *fileA = filesA->files + ctrA;
00266         const char *strA = fileA->name;
00267         /* skip directories */
00268         if(strA[strlen(strA)-1] != '/') {
00269             ret = alpm_list_add(ret, fileA);
00270         }
00271         ctrA++;
00272     }
00273 
00274     return ret;
00275 }
00276 
00277 /* Adds alpm_fileconflict_t to a conflicts list. Pass the conflicts list, the
00278  * conflicting file path, and either two packages or one package and NULL.
00279  */
00280 static alpm_list_t *add_fileconflict(alpm_handle_t *handle,
00281         alpm_list_t *conflicts, const char *filestr,
00282         alpm_pkg_t *pkg1, alpm_pkg_t *pkg2)
00283 {
00284     alpm_fileconflict_t *conflict;
00285     MALLOC(conflict, sizeof(alpm_fileconflict_t), goto error);
00286 
00287     STRDUP(conflict->target, pkg1->name, goto error);
00288     STRDUP(conflict->file, filestr, goto error);
00289     if(pkg2) {
00290         conflict->type = ALPM_FILECONFLICT_TARGET;
00291         STRDUP(conflict->ctarget, pkg2->name, goto error);
00292     } else {
00293         conflict->type = ALPM_FILECONFLICT_FILESYSTEM;
00294         STRDUP(conflict->ctarget, "", goto error);
00295     }
00296 
00297     conflicts = alpm_list_add(conflicts, conflict);
00298     _alpm_log(handle, ALPM_LOG_DEBUG, "found file conflict %s, packages %s and %s\n",
00299               filestr, pkg1->name, pkg2 ? pkg2->name : "(filesystem)");
00300 
00301     return conflicts;
00302 
00303 error:
00304     RET_ERR(handle, ALPM_ERR_MEMORY, conflicts);
00305 }
00306 
00307 void _alpm_fileconflict_free(alpm_fileconflict_t *conflict)
00308 {
00309     FREE(conflict->ctarget);
00310     FREE(conflict->file);
00311     FREE(conflict->target);
00312     FREE(conflict);
00313 }
00314 
00315 const alpm_file_t *_alpm_filelist_contains(alpm_filelist_t *filelist,
00316         const char *name)
00317 {
00318     size_t i;
00319     const alpm_file_t *file;
00320 
00321     if(!filelist) {
00322         return NULL;
00323     }
00324 
00325     for(file = filelist->files, i = 0; i < filelist->count; file++, i++) {
00326         if(strcmp(file->name, name) == 0) {
00327             return file;
00328         }
00329     }
00330     return NULL;
00331 }
00332 
00333 static int dir_belongsto_pkg(const char *root, const char *dirpath,
00334         alpm_pkg_t *pkg)
00335 {
00336     struct stat sbuf;
00337     char path[PATH_MAX];
00338     char abspath[PATH_MAX];
00339     struct dirent *ent = NULL;
00340     DIR *dir;
00341 
00342     snprintf(abspath, PATH_MAX, "%s%s", root, dirpath);
00343     dir = opendir(abspath);
00344     if(dir == NULL) {
00345         return 1;
00346     }
00347 
00348     while((ent = readdir(dir)) != NULL) {
00349         const char *name = ent->d_name;
00350 
00351         if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
00352             continue;
00353         }
00354         snprintf(path, PATH_MAX, "%s/%s", dirpath, name);
00355         snprintf(abspath, PATH_MAX, "%s%s", root, path);
00356         if(stat(abspath, &sbuf) != 0) {
00357             continue;
00358         }
00359         if(S_ISDIR(sbuf.st_mode)) {
00360             if(dir_belongsto_pkg(root, path, pkg)) {
00361                 continue;
00362             } else {
00363                 closedir(dir);
00364                 return 0;
00365             }
00366         } else {
00367             if(_alpm_filelist_contains(alpm_pkg_get_files(pkg), path)) {
00368                 continue;
00369             } else {
00370                 closedir(dir);
00371                 return 0;
00372             }
00373         }
00374     }
00375     closedir(dir);
00376     return 1;
00377 }
00378 
00379 /* Find file conflicts that may occur during the transaction with two checks:
00380  * 1: check every target against every target
00381  * 2: check every target against the filesystem */
00382 alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle,
00383         alpm_list_t *upgrade, alpm_list_t *remove)
00384 {
00385     alpm_list_t *i, *conflicts = NULL;
00386     size_t numtargs = alpm_list_count(upgrade);
00387     size_t current;
00388     size_t rootlen;
00389 
00390     if(!upgrade) {
00391         return NULL;
00392     }
00393 
00394     rootlen = strlen(handle->root);
00395 
00396     /* TODO this whole function needs a huge change, which hopefully will
00397      * be possible with real transactions. Right now we only do half as much
00398      * here as we do when we actually extract files in add.c with our 12
00399      * different cases. */
00400     for(current = 0, i = upgrade; i; i = i->next, current++) {
00401         alpm_pkg_t *p1 = i->data;
00402         alpm_list_t *j;
00403         alpm_filelist_t tmpfiles;
00404         alpm_pkg_t *dbpkg;
00405         size_t filenum;
00406 
00407         int percent = (current * 100) / numtargs;
00408         PROGRESS(handle, ALPM_PROGRESS_CONFLICTS_START, "", percent,
00409                  numtargs, current);
00410         /* CHECK 1: check every target against every target */
00411         _alpm_log(handle, ALPM_LOG_DEBUG, "searching for file conflicts: %s\n",
00412                 p1->name);
00413         for(j = i->next; j; j = j->next) {
00414             alpm_list_t *common_files;
00415             alpm_pkg_t *p2 = j->data;
00416             common_files = filelist_operation(alpm_pkg_get_files(p1),
00417                     alpm_pkg_get_files(p2), INTERSECT);
00418 
00419             if(common_files) {
00420                 alpm_list_t *k;
00421                 char path[PATH_MAX];
00422                 for(k = common_files; k; k = k->next) {
00423                     alpm_file_t *file = k->data;
00424                     snprintf(path, PATH_MAX, "%s%s", handle->root, file->name);
00425                     conflicts = add_fileconflict(handle, conflicts, path, p1, p2);
00426                     if(handle->pm_errno == ALPM_ERR_MEMORY) {
00427                         FREELIST(conflicts);
00428                         FREELIST(common_files);
00429                         return NULL;
00430                     }
00431                 }
00432                 alpm_list_free(common_files);
00433             }
00434         }
00435 
00436         /* CHECK 2: check every target against the filesystem */
00437         _alpm_log(handle, ALPM_LOG_DEBUG, "searching for filesystem conflicts: %s\n",
00438                 p1->name);
00439         dbpkg = _alpm_db_get_pkgfromcache(handle->db_local, p1->name);
00440 
00441         /* Do two different checks here. If the package is currently installed,
00442          * then only check files that are new in the new package. If the package
00443          * is not currently installed, then simply stat the whole filelist. Note
00444          * that the former list needs to be freed while the latter list should NOT
00445          * be freed. */
00446         if(dbpkg) {
00447             alpm_list_t *difference;
00448             /* older ver of package currently installed */
00449             difference = filelist_operation(alpm_pkg_get_files(p1),
00450                     alpm_pkg_get_files(dbpkg), DIFFERENCE);
00451             tmpfiles.count = alpm_list_count(difference);
00452             tmpfiles.files = alpm_list_to_array(difference, tmpfiles.count,
00453                     sizeof(alpm_file_t));
00454             alpm_list_free(difference);
00455         } else {
00456             /* no version of package currently installed */
00457             tmpfiles = *alpm_pkg_get_files(p1);
00458         }
00459 
00460         for(filenum = 0; filenum < tmpfiles.count; filenum++) {
00461             alpm_file_t *file = tmpfiles.files + filenum;
00462             const char *filestr = file->name;
00463             const char *relative_path;
00464             alpm_list_t *k;
00465             /* have we acted on this conflict? */
00466             int resolved_conflict = 0;
00467             struct stat lsbuf;
00468             char path[PATH_MAX];
00469             size_t pathlen;
00470 
00471             pathlen = snprintf(path, PATH_MAX, "%s%s", handle->root, filestr);
00472 
00473             /* stat the file - if it exists, do some checks */
00474             if(_alpm_lstat(path, &lsbuf) != 0) {
00475                 continue;
00476             }
00477 
00478             _alpm_log(handle, ALPM_LOG_DEBUG, "checking possible conflict: %s\n", path);
00479 
00480             if(S_ISDIR(file->mode)) {
00481                 struct stat sbuf;
00482                 if(S_ISDIR(lsbuf.st_mode)) {
00483                     _alpm_log(handle, ALPM_LOG_DEBUG, "file is a directory, not a conflict\n");
00484                     continue;
00485                 }
00486                 stat(path, &sbuf);
00487                 if(S_ISLNK(lsbuf.st_mode) && S_ISDIR(sbuf.st_mode)) {
00488                     _alpm_log(handle, ALPM_LOG_DEBUG,
00489                             "file is a symlink to a dir, hopefully not a conflict\n");
00490                     continue;
00491                 }
00492                 /* if we made it to here, we want all subsequent path comparisons to
00493                  * not include the trailing slash. This allows things like file ->
00494                  * directory replacements. */
00495                 path[pathlen - 1] = '\0';
00496             }
00497 
00498             relative_path = path + rootlen;
00499 
00500             /* Check remove list (will we remove the conflicting local file?) */
00501             for(k = remove; k && !resolved_conflict; k = k->next) {
00502                 alpm_pkg_t *rempkg = k->data;
00503                 if(rempkg && _alpm_filelist_contains(alpm_pkg_get_files(rempkg),
00504                             relative_path)) {
00505                     _alpm_log(handle, ALPM_LOG_DEBUG,
00506                             "local file will be removed, not a conflict\n");
00507                     resolved_conflict = 1;
00508                 }
00509             }
00510 
00511             /* Look at all the targets to see if file has changed hands */
00512             for(k = upgrade; k && !resolved_conflict; k = k->next) {
00513                 alpm_pkg_t *p2 = k->data;
00514                 if(!p2 || strcmp(p1->name, p2->name) == 0) {
00515                     continue;
00516                 }
00517                 alpm_pkg_t *localp2 = _alpm_db_get_pkgfromcache(handle->db_local, p2->name);
00518 
00519                 /* localp2->files will be removed (target conflicts are handled by CHECK 1) */
00520                 if(localp2 && _alpm_filelist_contains(alpm_pkg_get_files(localp2), filestr)) {
00521                     /* skip removal of file, but not add. this will prevent a second
00522                      * package from removing the file when it was already installed
00523                      * by its new owner (whether the file is in backup array or not */
00524                     handle->trans->skip_remove =
00525                         alpm_list_add(handle->trans->skip_remove, strdup(filestr));
00526                     _alpm_log(handle, ALPM_LOG_DEBUG,
00527                             "file changed packages, adding to remove skiplist\n");
00528                     resolved_conflict = 1;
00529                 }
00530             }
00531 
00532             /* check if all files of the dir belong to the installed pkg */
00533             if(!resolved_conflict && S_ISDIR(lsbuf.st_mode) && dbpkg) {
00534                 char *dir = malloc(strlen(filestr) + 2);
00535                 sprintf(dir, "%s/", filestr);
00536                 if(_alpm_filelist_contains(alpm_pkg_get_files(dbpkg), dir)) {
00537                     _alpm_log(handle, ALPM_LOG_DEBUG,
00538                             "check if all files in %s belongs to %s\n",
00539                             dir, dbpkg->name);
00540                     resolved_conflict = dir_belongsto_pkg(handle->root, filestr, dbpkg);
00541                 }
00542                 free(dir);
00543             }
00544 
00545             /* check if a component of the filepath was a link. canonicalize the path
00546              * and look for it in the old package. note that the actual file under
00547              * consideration cannot itself be a link, as it might be unowned- path
00548              * components can be safely checked as all directories are "unowned". */
00549             if(!resolved_conflict && dbpkg && !S_ISLNK(lsbuf.st_mode)) {
00550                 char *rpath = calloc(PATH_MAX, sizeof(char));
00551                 if(realpath(path, rpath)) {
00552                     const char *relative_rpath = rpath + rootlen;
00553                     if(_alpm_filelist_contains(alpm_pkg_get_files(dbpkg), relative_rpath)) {
00554                         _alpm_log(handle, ALPM_LOG_DEBUG,
00555                                 "package contained the resolved realpath\n");
00556                         resolved_conflict = 1;
00557                     }
00558                 }
00559                 free(rpath);
00560             }
00561 
00562             /* is the file unowned and in the backup list of the new package? */
00563             if(!resolved_conflict && _alpm_needbackup(filestr, p1)) {
00564                 alpm_list_t *local_pkgs = _alpm_db_get_pkgcache(handle->db_local);
00565                 int found = 0;
00566                 for(k = local_pkgs; k && !found; k = k->next) {
00567                     if(_alpm_filelist_contains(alpm_pkg_get_files(k->data), filestr)) {
00568                             found = 1;
00569                     }
00570                 }
00571                 if(!found) {
00572                     _alpm_log(handle, ALPM_LOG_DEBUG,
00573                             "file was unowned but in new backup list\n");
00574                     resolved_conflict = 1;
00575                 }
00576             }
00577 
00578             if(!resolved_conflict) {
00579                 conflicts = add_fileconflict(handle, conflicts, path, p1, NULL);
00580                 if(handle->pm_errno == ALPM_ERR_MEMORY) {
00581                     FREELIST(conflicts);
00582                     if(dbpkg) {
00583                         /* only freed if it was generated from filelist_operation() */
00584                         free(tmpfiles.files);
00585                     }
00586                     return NULL;
00587                 }
00588             }
00589         }
00590         if(dbpkg) {
00591             /* only freed if it was generated from filelist_operation() */
00592             free(tmpfiles.files);
00593         }
00594     }
00595     PROGRESS(handle, ALPM_PROGRESS_CONFLICTS_START, "", 100,
00596             numtargs, current);
00597 
00598     return conflicts;
00599 }
00600 
00601 /* vim: set ts=2 sw=2 noet: */