summaryrefslogtreecommitdiff
path: root/src/depcon.c
blob: f109384d4965de72dfaf7d4b13e113fec03a1f9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
/*
 * dpkg - main program for package management
 * depcon.c - dependency and conflict checking
 *
 * Copyright © 1994,1995 Ian Jackson <ian@chiark.greenend.org.uk>
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2,
 * or (at your option) any later version.
 *
 * This 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 dpkg; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <config.h>
#include <compat.h>

#include <dpkg/i18n.h>

#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>

#include <dpkg/dpkg.h>
#include <dpkg/dpkg-db.h>

#include "main.h"

struct cyclesofarlink {
  struct cyclesofarlink *back;
  struct pkginfo *pkg;
  struct deppossi *possi;
};

static int findbreakcyclerecursive(struct pkginfo *pkg, struct cyclesofarlink *sofar);

static int foundcyclebroken(struct cyclesofarlink *thislink,
                            struct cyclesofarlink *sofar,
                            struct pkginfo *dependedon,
                            struct deppossi *possi) {
  struct cyclesofarlink *sol;
  const char *postinstfilename;
  struct stat stab;

  if(!possi) return 0;
  /* We're investigating the dependency `possi' to see if it
   * is part of a loop.  To this end we look to see whether the
   * depended-on package is already one of the packages whose
   * dependencies we're searching.
   */
  for (sol=sofar; sol && sol->pkg != dependedon; sol=sol->back);

  /* If not, we do a recursive search on it to see what we find. */
  if (!sol)
    return findbreakcyclerecursive(dependedon, thislink);
  
  debug(dbg_depcon,"found cycle");
  /* Right, we now break one of the links.  We prefer to break
   * a dependency of a package without a postinst script, as
   * this is a null operation.  If this is not possible we break
   * the other link in the recursive calling tree which mentions
   * this package (this being the first package involved in the
   * cycle).  It doesn't particularly matter which we pick, but if
   * we break the earliest dependency we came across we may be
   * able to do something straight away when findbreakcycle returns.
   */
  sofar= thislink;
  for (sol= sofar; !(sol != sofar && sol->pkg == dependedon); sol=sol->back) {
    postinstfilename= pkgadminfile(sol->pkg,POSTINSTFILE);
    if (lstat(postinstfilename,&stab)) {
      if (errno == ENOENT) break;
      ohshite(_("unable to check for existence of `%.250s'"),postinstfilename);
    }
  }
  /* Now we have either a package with no postinst, or the other
   * occurrence of the current package in the list.
   */
  sol->possi->cyclebreak= 1;
  debug(dbg_depcon,"cycle broken at %s -> %s\n",
        sol->possi->up->up->name, sol->possi->ed->name);
  return 1;
}

static int findbreakcyclerecursive(struct pkginfo *pkg, struct cyclesofarlink *sofar) {
  /* Cycle breaking works recursively down the package dependency
   * tree.  `sofar' is the list of packages we've descended down
   * already - if we encounter any of its packages again in a
   * dependency we have found a cycle.
   */
  struct cyclesofarlink thislink, *sol;
  struct dependency *dep;
  struct deppossi *possi, *providelink;
  struct pkginfo *provider;

  if (pkg->color == black)
    return 0;
  pkg->color = gray;
  
  if (f_debug & dbg_depcondetail) {
    struct varbuf str_pkgs = VARBUF_INIT;

    for (sol = sofar; sol; sol = sol->back) {
      varbufaddstr(&str_pkgs, " <- ");
      varbufaddstr(&str_pkgs, sol->pkg->name);
    }
    varbufaddc(&str_pkgs, '\0');
    debug(dbg_depcondetail, "findbreakcyclerecursive %s %s", pkg->name,
          str_pkgs.buf);
    varbuffree(&str_pkgs);
  }
  thislink.pkg= pkg;
  thislink.back= sofar;
  thislink.possi = NULL;
  for (dep= pkg->installed.depends; dep; dep= dep->next) {
    if (dep->type != dep_depends && dep->type != dep_predepends) continue;
    for (possi= dep->list; possi; possi= possi->next) {
      /* Don't find the same cycles again. */
      if (possi->cyclebreak) continue;
      thislink.possi= possi;
      if (foundcyclebroken(&thislink,sofar,possi->ed,possi)) return 1;
      /* Right, now we try all the providers ... */
      for (providelink= possi->ed->installed.depended;
           providelink;
           providelink= providelink->nextrev) {
        if (providelink->up->type != dep_provides) continue;
        provider= providelink->up->up;
        if (provider->clientdata->istobe == itb_normal) continue;
        /* We don't break things at `provides' links, so `possi' is
         * still the one we use.
         */
        if (foundcyclebroken(&thislink,sofar,provider,possi)) return 1;
      }
    }
  }
  /* Nope, we didn't find a cycle to break. */
  pkg->color = black;
  return 0;
}

int findbreakcycle(struct pkginfo *pkg) {
  struct pkgiterator *iter;
  struct pkginfo *tpkg;
	
  /* Clear the visited flag of all packages before we traverse them. */
  for (iter = iterpkgstart(); (tpkg=iterpkgnext(iter)); ) {
    tpkg->color = white;
  }
  iterpkgend(iter);

  return findbreakcyclerecursive(pkg, NULL);
}

void describedepcon(struct varbuf *addto, struct dependency *dep) {
  const char *fmt;
  struct varbuf depstr = VARBUF_INIT;

  switch (dep->type) {
  case dep_depends:
    fmt = _("%s depends on %s");
    break;
  case dep_predepends:
    fmt = _("%s pre-depends on %s");
    break;
  case dep_recommends:
    fmt = _("%s recommends %s");
    break;
  case dep_suggests:
    fmt = _("%s suggests %s");
    break;
  case dep_breaks:
    fmt = _("%s breaks %s");
    break;
  case dep_conflicts:
    fmt = _("%s conflicts with %s");
    break;
  case dep_enhances:
    fmt = _("%s enhances %s");
    break;
  default:
    internerr("unknown deptype '%d'", dep->type);
  }

  varbufdependency(&depstr, dep);
  varbufaddc(&depstr, 0);

  varbufprintf(addto, fmt, dep->up->name, depstr.buf);
  varbuffree(&depstr);
}
  
int depisok(struct dependency *dep, struct varbuf *whynot,
            struct pkginfo **canfixbyremove, int allowunconfigd) {
  /* *whynot must already have been initialised; it need not be
   * empty though - it will be reset before use.
   * If depisok returns 0 for `not OK' it will contain a description,
   * newline-terminated BUT NOT NULL-TERMINATED, of the reason.
   * If depisok returns 1 it will contain garbage.
   * allowunconfigd should be non-zero during the `Pre-Depends' checking
   * before a package is unpacked, when it is sufficient for the package
   * to be unpacked provided that both the unpacked and previously-configured
   * versions are acceptable.
   * On 0 return (`not OK'), *canfixbyremove refers to a package which
   * if removed (dep_conflicts) or deconfigured (dep_breaks) will fix
   * the problem.  Caller may pass 0 for canfixbyremove and need not
   * initialise *canfixbyremove.
   */
  struct deppossi *possi;
  struct deppossi *provider;
  int nconflicts;

  /* Use this buffer so that when internationalisation comes along we
   * don't have to rewrite the code completely, only redo the sprintf strings
   * (assuming we have the fancy argument-number-specifiers).
   * Allow 250x3 for package names, versions, &c, + 250 for ourselves.
   */
  char linebuf[1024];

  assert(dep->type == dep_depends || dep->type == dep_predepends ||
	 dep->type == dep_breaks || dep->type == dep_conflicts ||
	 dep->type == dep_recommends || dep->type == dep_suggests ||
	 dep->type == dep_enhances);
  
  if (canfixbyremove)
    *canfixbyremove = NULL;

  /* The dependency is always OK if we're trying to remove the depend*ing*
   * package.
   */
  switch (dep->up->clientdata->istobe) {
  case itb_remove: case itb_deconfigure:
    return 1;
  case itb_normal:
    /* Only installed packages can be make dependency problems */
    switch (dep->up->status) {
    case stat_installed:
    case stat_triggerspending:
    case stat_triggersawaited:
      break;
    case stat_notinstalled: case stat_configfiles: case stat_halfinstalled:
    case stat_halfconfigured: case stat_unpacked:
      return 1;
    default:
      internerr("unknown status depending '%d'", dep->up->status);
    }
    break;
  case itb_installnew: case itb_preinstall:
    break;
  default:
    internerr("unknown istobe depending '%d'", dep->up->clientdata->istobe);
  }

  /* Describe the dependency, in case we have to moan about it. */
  varbufreset(whynot);
  varbufaddc(whynot, ' ');
  describedepcon(whynot, dep);
  varbufaddc(whynot,'\n');
  
  /* TODO: check dep_enhances as well (WTA) */
  if (dep->type == dep_depends || dep->type == dep_predepends ||
      dep->type == dep_recommends || dep->type == dep_suggests ) {
    
    /* Go through the alternatives.  As soon as we find one that
     * we like, we return `1' straight away.  Otherwise, when we get to
     * the end we'll have accumulated all the reasons in whynot and
     * can return `0'.
     */

    for (possi= dep->list; possi; possi= possi->next) {
      switch (possi->ed->clientdata->istobe) {
      case itb_remove:
        sprintf(linebuf,_("  %.250s is to be removed.\n"),possi->ed->name);
        break;
      case itb_deconfigure:
        sprintf(linebuf,_("  %.250s is to be deconfigured.\n"),possi->ed->name);
        break;
      case itb_installnew:
        if (versionsatisfied(&possi->ed->available,possi)) return 1;
        sprintf(linebuf,_("  %.250s is to be installed, but is version %.250s.\n"),
                possi->ed->name,
                versiondescribe(&possi->ed->available.version,vdew_nonambig));
        break;
      case itb_normal: case itb_preinstall:
        switch (possi->ed->status) {
        case stat_installed:
        case stat_triggerspending:
          if (versionsatisfied(&possi->ed->installed,possi)) return 1;
          sprintf(linebuf,_("  %.250s is installed, but is version %.250s.\n"),
                  possi->ed->name,
                  versiondescribe(&possi->ed->installed.version,vdew_nonambig));
          break;
        case stat_notinstalled:
          /* Don't say anything about this yet - it might be a virtual package.
           * Later on, if nothing has put anything in linebuf, we know that it
           * isn't and issue a diagnostic then.
           */
          *linebuf = '\0';
          break;
        case stat_unpacked:
        case stat_halfconfigured:
        case stat_triggersawaited:
          if (allowunconfigd) {
            if (!informativeversion(&possi->ed->configversion)) {
              sprintf(linebuf, _("  %.250s is unpacked, but has never been configured.\n"),
                      possi->ed->name);
              break;
            } else if (!versionsatisfied(&possi->ed->installed, possi)) {
              sprintf(linebuf, _("  %.250s is unpacked, but is version %.250s.\n"),
                      possi->ed->name,
                      versiondescribe(&possi->ed->available.version,vdew_nonambig));
              break;
            } else if (!versionsatisfied3(&possi->ed->configversion,
                                          &possi->version,possi->verrel)) {
              sprintf(linebuf, _("  %.250s latest configured version is %.250s.\n"),
                      possi->ed->name,
                      versiondescribe(&possi->ed->configversion,vdew_nonambig));
              break;
            } else {
              return 1;
            }
          }
          /* Fall through. */
        default:
          sprintf(linebuf, _("  %.250s is %s.\n"),
                  possi->ed->name, gettext(statusstrings[possi->ed->status]));
          break;
        }
        break;
      default:
        internerr("unknown istobe depended '%d'", possi->ed->clientdata->istobe);
      }
      varbufaddstr(whynot, linebuf);

      /* If there was no version specified we try looking for Providers. */
      if (possi->verrel == dvr_none) {
        
        /* See if the package we're about to install Provides it. */
        for (provider= possi->ed->available.depended;
             provider;
             provider= provider->nextrev) {
          if (provider->up->type != dep_provides) continue;
          if (provider->up->up->clientdata->istobe == itb_installnew) return 1;
        }

        /* Now look at the packages already on the system. */
        for (provider= possi->ed->installed.depended;
             provider;
             provider= provider->nextrev) {
          if (provider->up->type != dep_provides) continue;
          
          switch (provider->up->up->clientdata->istobe) {
          case itb_installnew:
            /* Don't pay any attention to the Provides field of the
             * currently-installed version of the package we're trying
             * to install.  We dealt with that by using the available
             * information above.
             */
            continue; 
          case itb_remove:
            sprintf(linebuf, _("  %.250s provides %.250s but is to be removed.\n"),
                    provider->up->up->name, possi->ed->name);
            break;
          case itb_deconfigure:
            sprintf(linebuf, _("  %.250s provides %.250s but is to be deconfigured.\n"),
                    provider->up->up->name, possi->ed->name);
            break;
          case itb_normal: case itb_preinstall:
            if (provider->up->up->status == stat_installed) return 1;
            sprintf(linebuf, _("  %.250s provides %.250s but is %s.\n"),
                    provider->up->up->name, possi->ed->name,
                    gettext(statusstrings[provider->up->up->status]));
            break;
          default:
            internerr("unknown istobe provider '%d'",
                      provider->up->up->clientdata->istobe);
          }
          varbufaddstr(whynot, linebuf);
        }
        
        if (!*linebuf) {
          /* If the package wasn't installed at all, and we haven't said
           * yet why this isn't satisfied, we should say so now.
           */
          sprintf(linebuf, _("  %.250s is not installed.\n"), possi->ed->name);
          varbufaddstr(whynot, linebuf);
        }
      }
    }

    return 0;

  } else {
    
    /* It's conflicts or breaks.  There's only one main alternative,
     * but we also have to consider Providers.  We return `0' as soon
     * as we find something that matches the conflict, and only describe
     * it then.  If we get to the end without finding anything we return `1'.
     */

    possi= dep->list;
    nconflicts= 0;

    if (possi->ed != possi->up->up) {
      /* If the package conflicts with or breaks itself it must mean
       * other packages which provide the same virtual name.  We
       * therefore don't look at the real package and go on to the
       * virtual ones.
       */
      
      switch (possi->ed->clientdata->istobe) {
      case itb_remove:
        break;
      case itb_installnew:
        if (!versionsatisfied(&possi->ed->available, possi)) break;
        sprintf(linebuf, _("  %.250s (version %.250s) is to be installed.\n"),
                possi->ed->name,
                versiondescribe(&possi->ed->available.version,vdew_nonambig));
        varbufaddstr(whynot, linebuf);
        if (!canfixbyremove) return 0;
        nconflicts++;
        *canfixbyremove= possi->ed;
        break;
      case itb_deconfigure:
        if (dep->type == dep_breaks) break; /* already deconfiguring this */
        /* fall through */
      case itb_normal: case itb_preinstall:
        switch (possi->ed->status) {
        case stat_notinstalled: case stat_configfiles:
          break;
        case stat_halfinstalled: case stat_unpacked:
        case stat_halfconfigured:
          if (dep->type == dep_breaks) break; /* no problem */
        case stat_installed:
        case stat_triggerspending:
        case stat_triggersawaited:
          if (!versionsatisfied(&possi->ed->installed, possi)) break;
          sprintf(linebuf, _("  %.250s (version %.250s) is present and %s.\n"),
                  possi->ed->name,
                  versiondescribe(&possi->ed->installed.version,vdew_nonambig),
                  gettext(statusstrings[possi->ed->status]));
          varbufaddstr(whynot, linebuf);
          if (!canfixbyremove) return 0;
          nconflicts++;
          *canfixbyremove= possi->ed;
        }
        break;
      default:
        internerr("unknown istobe conflict '%d'",
                  possi->ed->clientdata->istobe);
      }
    }

    /* If there was no version specified we try looking for Providers. */
    if (possi->verrel == dvr_none) {
        
      /* See if the package we're about to install Provides it. */
      for (provider= possi->ed->available.depended;
           provider;
           provider= provider->nextrev) {
        if (provider->up->type != dep_provides) continue;
        if (provider->up->up->clientdata->istobe != itb_installnew) continue;
        if (provider->up->up == dep->up) continue; /* conflicts and provides the same */
        sprintf(linebuf, _("  %.250s provides %.250s and is to be installed.\n"),
                provider->up->up->name, possi->ed->name);
        varbufaddstr(whynot, linebuf);
        /* We can't remove the one we're about to install: */
        if (canfixbyremove)
          *canfixbyremove = NULL;
        return 0;
      }

      /* Now look at the packages already on the system. */
      for (provider= possi->ed->installed.depended;
           provider;
           provider= provider->nextrev) {
        if (provider->up->type != dep_provides) continue;
          
        if (provider->up->up == dep->up) continue; /* conflicts and provides the same */
        
        switch (provider->up->up->clientdata->istobe) {
        case itb_installnew:
          /* Don't pay any attention to the Provides field of the
           * currently-installed version of the package we're trying
           * to install.  We dealt with that package by using the
           * available information above.
           */
          continue; 
        case itb_remove:
          continue;
        case itb_deconfigure:
          if (dep->type == dep_breaks) continue; /* already deconfiguring */
        case itb_normal: case itb_preinstall:
          switch (provider->up->up->status) {
          case stat_notinstalled: case stat_configfiles:
            continue;
          case stat_halfinstalled: case stat_unpacked:
          case stat_halfconfigured:
            if (dep->type == dep_breaks) break; /* no problem */
          case stat_installed:
          case stat_triggerspending:
          case stat_triggersawaited:
            sprintf(linebuf,
                    _("  %.250s provides %.250s and is present and %s.\n"),
                    provider->up->up->name, possi->ed->name,
                    gettext(statusstrings[provider->up->up->status]));
            varbufaddstr(whynot, linebuf);
            if (!canfixbyremove) return 0;
            nconflicts++;
            *canfixbyremove= provider->up->up;
            break;
          }
          break;
        default:
          internerr("unknown istobe conflict provider '%d'",
                    provider->up->up->clientdata->istobe);
        }
      }
    }

    if (!nconflicts) return 1;
    if (nconflicts > 1)
      *canfixbyremove = NULL;
    return 0;

  } /* if (dependency) {...} else {...} */
}