divertcmd.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. /*
  2. * dpkg-divert - override a package's version of a file
  3. *
  4. * Copyright © 1995 Ian Jackson
  5. * Copyright © 2000, 2001 Wichert Akkerman
  6. * Copyright © 2010 Guillem Jover <guillem@debian.org>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include <config.h>
  22. #include <compat.h>
  23. #include <sys/types.h>
  24. #include <sys/stat.h>
  25. #include <errno.h>
  26. #if HAVE_LOCALE_H
  27. #include <locale.h>
  28. #endif
  29. #include <fcntl.h>
  30. #include <fnmatch.h>
  31. #include <string.h>
  32. #include <stdlib.h>
  33. #include <unistd.h>
  34. #include <dpkg/i18n.h>
  35. #include <dpkg/dpkg.h>
  36. #include <dpkg/dpkg-db.h>
  37. #include <dpkg/file.h>
  38. #include <dpkg/glob.h>
  39. #include <dpkg/buffer.h>
  40. #include <dpkg/myopt.h>
  41. #include "filesdb.h"
  42. const char thisname[] = "dpkg-divert";
  43. const char printforhelp[] = N_("Use --help for help about querying packages.");
  44. const char *admindir;
  45. static bool opt_pkgname_match_any = true;
  46. static const char *opt_pkgname = NULL;
  47. static const char *opt_divertto = NULL;
  48. static int opt_verbose = 1;
  49. static int opt_test = 0;
  50. static int opt_rename = 0;
  51. static void
  52. printversion(const struct cmdinfo *cip, const char *value)
  53. {
  54. printf(_("Debian %s version %s.\n"), thisname, DPKG_VERSION_ARCH);
  55. printf(_(
  56. "Copyright (C) 1995 Ian Jackson.\n"
  57. "Copyright (C) 2000,2001 Wichert Akkerman.\n"
  58. "Copyright (C) 2010 Guillem Jover.\n"));
  59. printf(_(
  60. "This is free software; see the GNU General Public License version 2 or\n"
  61. "later for copying conditions. There is NO warranty.\n"));
  62. m_output(stdout, _("<standard output>"));
  63. exit(0);
  64. }
  65. static void
  66. usage(const struct cmdinfo *cip, const char *value)
  67. {
  68. printf(_(
  69. "Usage: %s [<option> ...] <command>\n"
  70. "\n"), thisname);
  71. printf(_(
  72. "Commands:\n"
  73. " [--add] <file> add a diversion.\n"
  74. " --remove <file> remove the diversion.\n"
  75. " --list [<glob-pattern>] show file diversions.\n"
  76. " --listpackage <file> show what package diverts the file.\n"
  77. " --truename <file> return the diverted file.\n"
  78. "\n"));
  79. printf(_(
  80. "Options:\n"
  81. " --package <package> name of the package whose copy of <file> will not\n"
  82. " be diverted.\n"
  83. " --local all packages' versions are diverted.\n"
  84. " --divert <divert-to> the name used by other packages' versions.\n"
  85. " --rename actually move the file aside (or back).\n"
  86. " --admindir <directory> set the directory with the diversions file.\n"
  87. " --test don't do anything, just demonstrate.\n"
  88. " --quiet quiet operation, minimal output.\n"
  89. " --help show this help message.\n"
  90. " --version show the version.\n"
  91. "\n"));
  92. printf(_(
  93. "When adding, default is --local and --divert <original>.distrib.\n"
  94. "When removing, --package or --local and --divert must match if specified.\n"
  95. "Package preinst/postrm scripts should always specify --package and --divert.\n"));
  96. m_output(stdout, _("<standard output>"));
  97. exit(0);
  98. }
  99. struct file {
  100. const char *name;
  101. enum {
  102. file_stat_invalid,
  103. file_stat_valid,
  104. file_stat_nofile,
  105. } stat_state;
  106. struct stat stat;
  107. };
  108. static void
  109. file_init(struct file *f, const char *filename)
  110. {
  111. f->name = filename;
  112. f->stat_state = file_stat_invalid;
  113. }
  114. static void
  115. file_stat(struct file *f)
  116. {
  117. int ret;
  118. if (f->stat_state != file_stat_invalid)
  119. return;
  120. ret = lstat(f->name, &f->stat);
  121. if (ret && errno != ENOENT)
  122. ohshite(_("cannot stat file '%s'"), f->name);
  123. if (ret == 0)
  124. f->stat_state = file_stat_valid;
  125. else
  126. f->stat_state = file_stat_nofile;
  127. }
  128. static void
  129. check_writable_dir(struct file *f)
  130. {
  131. char *tmpname;
  132. int tmpfd;
  133. m_asprintf(&tmpname, "%s%s", f->name, ".dpkg-divert.tmp");
  134. tmpfd = creat(tmpname, 0600);
  135. if (tmpfd < 0)
  136. ohshite(_("error checking '%s'"), f->name);
  137. close(tmpfd);
  138. unlink(tmpname);
  139. free(tmpname);
  140. }
  141. static bool
  142. check_rename(struct file *src, struct file *dst)
  143. {
  144. file_stat(src);
  145. /* If the source file is not present and we are not going to do
  146. * the rename anyway there's no point in checking any further. */
  147. if (src->stat_state == file_stat_nofile)
  148. return false;
  149. file_stat(dst);
  150. /*
  151. * Unfortunately we have to check for write access in both places,
  152. * just having +w is not enough, since people do mount things RO,
  153. * and we need to fail before we start mucking around with things.
  154. * So we open a file with the same name as the diversions but with
  155. * an extension that (hopefully) wont overwrite anything. If it
  156. * succeeds, we assume a writable filesystem.
  157. */
  158. check_writable_dir(src);
  159. check_writable_dir(dst);
  160. if (src->stat_state == file_stat_valid &&
  161. dst->stat_state == file_stat_valid &&
  162. !(src->stat.st_dev == dst->stat.st_dev &&
  163. src->stat.st_ino == dst->stat.st_ino))
  164. ohshit(_("rename involves overwriting `%s' with\n"
  165. " different file `%s', not allowed"),
  166. dst->name, src->name);
  167. return true;
  168. }
  169. static int
  170. file_copy(const char *src, const char *dst)
  171. {
  172. int srcfd, dstfd;
  173. srcfd = open(src, O_RDONLY);
  174. if (srcfd < 0)
  175. return -1;
  176. dstfd = creat(dst, 0600);
  177. if (dstfd < 0)
  178. return -1;
  179. /* FIXME: leaves a dangling destination file on error. */
  180. fd_fd_copy(srcfd, dstfd, -1, _("file copy"));
  181. close(srcfd);
  182. if (fsync(dstfd))
  183. return -1;
  184. if (close(dstfd))
  185. return -1;
  186. file_copy_perms(src, dst);
  187. return 0;
  188. }
  189. static int
  190. rename_mv(const char *src, const char *dst)
  191. {
  192. char *tmpdst;
  193. if (rename(src, dst) == 0)
  194. return 0;
  195. m_asprintf(&tmpdst, "%s%s", dst, ".dpkg-divert.tmp");
  196. /* If a simple rename didn't work try an atomic copy, rename, unlink
  197. * instead. */
  198. if (file_copy(src, tmpdst) != 0)
  199. return -1;
  200. if (rename(tmpdst, dst) != 0)
  201. return -1;
  202. free(tmpdst);
  203. return -1;
  204. }
  205. static void
  206. file_rename(struct file *src, struct file *dst)
  207. {
  208. if (src->stat_state == file_stat_nofile)
  209. return;
  210. if (dst->stat_state == file_stat_valid) {
  211. if (unlink(src->name))
  212. ohshite(_("rename: remove duplicate old link '%s'"),
  213. src->name);
  214. } else {
  215. if (rename_mv(src->name, dst->name))
  216. ohshite(_("cannot rename '%s' to '%s'"),
  217. src->name, dst->name);
  218. }
  219. }
  220. static const char *
  221. diversion_pkg_name(struct diversion *d)
  222. {
  223. if (d->pkg == NULL)
  224. return ":";
  225. else
  226. return d->pkg->name;
  227. }
  228. static const char *
  229. varbuf_diversion(struct varbuf *str, const char *pkgname,
  230. const char *filename, const char *divertto)
  231. {
  232. varbuf_reset(str);
  233. if (pkgname == NULL) {
  234. if (divertto == NULL)
  235. varbuf_printf(str, _("local diversion of %s"), filename);
  236. else
  237. varbuf_printf(str, _("local diversion of %s to %s"),
  238. filename, divertto);
  239. } else {
  240. if (divertto == NULL)
  241. varbuf_printf(str, _("diversion of %s by %s"),
  242. filename, pkgname);
  243. else
  244. varbuf_printf(str, _("diversion of %s to %s by %s"),
  245. filename, divertto, pkgname);
  246. }
  247. return str->buf;
  248. }
  249. static const char *
  250. diversion_current(const char *filename)
  251. {
  252. static struct varbuf str = VARBUF_INIT;
  253. if (opt_pkgname_match_any) {
  254. varbuf_reset(&str);
  255. if (opt_divertto == NULL)
  256. varbuf_printf(&str, _("any diversion of %s"), filename);
  257. else
  258. varbuf_printf(&str, _("any diversion of %s to %s"),
  259. filename, opt_divertto);
  260. } else {
  261. return varbuf_diversion(&str, opt_pkgname, filename, opt_divertto);
  262. }
  263. return str.buf;
  264. }
  265. static const char *
  266. diversion_describe(struct diversion *d)
  267. {
  268. static struct varbuf str = VARBUF_INIT;
  269. const char *pkgname;
  270. const char *name_from, *name_to;
  271. if (d->camefrom) {
  272. name_from = d->camefrom->name;
  273. name_to = d->camefrom->divert->useinstead->name;
  274. } else {
  275. name_from = d->useinstead->divert->camefrom->name;
  276. name_to = d->useinstead->name;
  277. }
  278. if (d->pkg == NULL)
  279. pkgname = NULL;
  280. else
  281. pkgname = d->pkg->name;
  282. return varbuf_diversion(&str, pkgname, name_from, name_to);
  283. }
  284. static void
  285. divertdb_write(void)
  286. {
  287. char *dbname, *dbname_new, *dbname_old;
  288. FILE *dbfile;
  289. struct fileiterator *iter;
  290. struct filenamenode *namenode;
  291. m_asprintf(&dbname, "%s/%s", admindir, DIVERSIONSFILE);
  292. m_asprintf(&dbname_new, "%s%s", dbname, NEWDBEXT);
  293. m_asprintf(&dbname_old, "%s%s", dbname, OLDDBEXT);
  294. dbfile = fopen(dbname_new, "w");
  295. if (!dbfile)
  296. ohshite(_("cannot create new %s file"), DIVERSIONSFILE);
  297. chmod(dbname_new, 0644);
  298. iter = iterfilestart();
  299. while ((namenode = iterfilenext(iter))) {
  300. struct diversion *d = namenode->divert;
  301. if (d == NULL || d->useinstead == NULL)
  302. continue;
  303. fprintf(dbfile, "%s\n%s\n%s\n",
  304. d->useinstead->divert->camefrom->name,
  305. d->useinstead->name,
  306. diversion_pkg_name(d));
  307. }
  308. iterfileend(iter);
  309. if (fflush(dbfile))
  310. ohshite(_("unable to flush file '%s'"), dbname_new);
  311. if (fsync(fileno(dbfile)))
  312. ohshite(_("unable to sync file '%s'"), dbname_new);
  313. if (fclose(dbfile))
  314. ohshite(_("unable to close file '%s'"), dbname_new);
  315. if (unlink(dbname_old) && errno != ENOENT)
  316. ohshite(_("error removing old diversions-old"));
  317. if (link(dbname, dbname_old) && errno != ENOENT)
  318. ohshite(_("error creating new diversions-old"));
  319. if (rename(dbname_new, dbname))
  320. ohshite(_("error installing new diversions"));
  321. free(dbname);
  322. free(dbname_new);
  323. free(dbname_old);
  324. }
  325. static int
  326. diversion_add(const char *const *argv)
  327. {
  328. const char *filename = argv[0];
  329. struct file file_from, file_to;
  330. struct diversion *contest, *altname;
  331. struct filenamenode *fnn_from, *fnn_to;
  332. struct pkginfo *pkg;
  333. opt_pkgname_match_any = false;
  334. /* Handle filename. */
  335. if (!filename || argv[1])
  336. badusage(_("--%s needs a single argument"), cipaction->olong);
  337. if (filename[0] != '/')
  338. badusage(_("filename \"%s\" is not absolute"), filename);
  339. if (strchr(filename, '\n') != NULL)
  340. badusage(_("file may not contain newlines"));
  341. file_init(&file_from, filename);
  342. file_stat(&file_from);
  343. if (file_from.stat_state == file_stat_valid &&
  344. S_ISDIR(file_from.stat.st_mode))
  345. badusage(_("Cannot divert directories"));
  346. fnn_from = findnamenode(filename, 0);
  347. /* Handle divertto. */
  348. if (opt_divertto == NULL) {
  349. char *str;
  350. m_asprintf(&str, "%s.distrib", filename);
  351. opt_divertto = str;
  352. }
  353. if (opt_divertto[0] != '/')
  354. badusage(_("filename \"%s\" is not absolute"), opt_divertto);
  355. if (strcmp(filename, opt_divertto) == 0)
  356. badusage(_("cannot divert file '%s' to itself"), filename);
  357. file_init(&file_to, opt_divertto);
  358. fnn_to = findnamenode(opt_divertto, 0);
  359. /* Handle package name. */
  360. if (opt_pkgname == NULL)
  361. pkg = NULL;
  362. else
  363. pkg = pkg_db_find(opt_pkgname);
  364. /* Check we are not stomping over an existing diversion. */
  365. if (fnn_from->divert || fnn_to->divert) {
  366. if (fnn_to->divert && fnn_to->divert->camefrom &&
  367. strcmp(fnn_to->divert->camefrom->name, filename) == 0 &&
  368. fnn_from->divert && fnn_from->divert->useinstead &&
  369. strcmp(fnn_from->divert->useinstead->name, opt_divertto) == 0 &&
  370. fnn_from->divert->pkg == pkg) {
  371. if (opt_verbose > 0)
  372. printf(_("Leaving '%s'\n"),
  373. diversion_describe(fnn_from->divert));
  374. exit(0);
  375. }
  376. ohshit(_("`%s' clashes with `%s'"),
  377. diversion_current(filename),
  378. fnn_from->divert ?
  379. diversion_describe(fnn_from->divert) :
  380. diversion_describe(fnn_to->divert));
  381. }
  382. /* Create new diversion. */
  383. contest = nfmalloc(sizeof(*contest));
  384. altname = nfmalloc(sizeof(*altname));
  385. altname->camefrom = fnn_from;
  386. altname->camefrom->divert = contest;
  387. altname->useinstead = NULL;
  388. altname->pkg = pkg;
  389. contest->useinstead = fnn_to;
  390. contest->useinstead->divert = altname;
  391. contest->camefrom = NULL;
  392. contest->pkg = pkg;
  393. /* Update database and file system if needed. */
  394. if (opt_verbose > 0)
  395. printf(_("Adding '%s'\n"), diversion_describe(contest));
  396. if (opt_rename)
  397. opt_rename = check_rename(&file_from, &file_to);
  398. if (!opt_test) {
  399. divertdb_write();
  400. if (opt_rename)
  401. file_rename(&file_from, &file_to);
  402. }
  403. return 0;
  404. }
  405. static int
  406. diversion_remove(const char *const *argv)
  407. {
  408. const char *filename = argv[0];
  409. struct filenamenode *namenode;
  410. struct diversion *contest, *altname;
  411. struct file file_from, file_to;
  412. struct pkginfo *pkg;
  413. if (!filename || argv[1])
  414. badusage(_("--%s needs a single argument"), cipaction->olong);
  415. namenode = findnamenode(filename, fnn_nonew);
  416. if (namenode == NULL || namenode->divert == NULL ||
  417. namenode->divert->useinstead == NULL) {
  418. if (opt_verbose > 0)
  419. printf(_("No diversion '%s', none removed.\n"),
  420. diversion_current(filename));
  421. return 0;
  422. }
  423. if (opt_pkgname == NULL)
  424. pkg = NULL;
  425. else
  426. pkg = pkg_db_find(opt_pkgname);
  427. contest = namenode->divert;
  428. altname = contest->useinstead->divert;
  429. if (opt_divertto != NULL &&
  430. strcmp(opt_divertto, contest->useinstead->name) != 0)
  431. ohshit(_("mismatch on divert-to\n"
  432. " when removing `%s'\n"
  433. " found `%s'"),
  434. diversion_current(filename),
  435. diversion_describe(contest));
  436. if (!opt_pkgname_match_any && pkg != contest->pkg)
  437. ohshit(_("mismatch on package\n"
  438. " when removing `%s'\n"
  439. " found `%s'"),
  440. diversion_current(filename),
  441. diversion_describe(contest));
  442. if (opt_verbose > 0)
  443. printf(_("Removing '%s'\n"), diversion_describe(contest));
  444. file_init(&file_from, altname->camefrom->name);
  445. file_init(&file_to, contest->useinstead->name);
  446. /* Remove entries from database. */
  447. contest->useinstead->divert = NULL;
  448. altname->camefrom->divert = NULL;
  449. if (opt_rename)
  450. opt_rename = check_rename(&file_to, &file_from);
  451. if (opt_rename && !opt_test)
  452. file_rename(&file_to, &file_from);
  453. if (!opt_test)
  454. divertdb_write();
  455. return 0;
  456. }
  457. static int
  458. diversion_list(const char *const *argv)
  459. {
  460. struct fileiterator *iter;
  461. struct filenamenode *namenode;
  462. struct glob_node *glob_list = NULL;
  463. const char *pattern;
  464. while ((pattern = *argv++))
  465. glob_list_prepend(&glob_list, m_strdup(pattern));
  466. if (glob_list == NULL)
  467. glob_list_prepend(&glob_list, m_strdup("*"));
  468. iter = iterfilestart();
  469. while ((namenode = iterfilenext(iter))) {
  470. struct glob_node *g;
  471. struct diversion *contest = namenode->divert;
  472. struct diversion *altname;
  473. const char *pkg_name;
  474. if (contest->useinstead == NULL)
  475. continue;
  476. altname = contest->useinstead->divert;
  477. pkg_name = diversion_pkg_name(contest);
  478. for (g = glob_list; g; g = g->next) {
  479. if (fnmatch(g->pattern, pkg_name, 0) == 0 ||
  480. fnmatch(g->pattern, contest->useinstead->name, 0) == 0 ||
  481. fnmatch(g->pattern, altname->camefrom->name, 0) == 0) {
  482. printf("%s\n", diversion_describe(contest));
  483. break;
  484. }
  485. }
  486. }
  487. iterfileend(iter);
  488. glob_list_free(glob_list);
  489. return 0;
  490. }
  491. static int
  492. diversion_truename(const char *const *argv)
  493. {
  494. const char *filename = argv[0];
  495. struct filenamenode *namenode;
  496. if (!filename || argv[1])
  497. badusage(_("--%s needs a single argument"), cipaction->olong);
  498. namenode = findnamenode(filename, fnn_nonew);
  499. /* Print the given name if file is not diverted. */
  500. if (namenode && namenode->divert->useinstead)
  501. printf("%s\n", namenode->divert->useinstead->name);
  502. else
  503. printf("%s\n", filename);
  504. return 0;
  505. }
  506. static int
  507. diversion_listpackage(const char *const *argv)
  508. {
  509. const char *filename = argv[0];
  510. struct filenamenode *namenode;
  511. if (!filename || argv[1])
  512. badusage(_("--%s needs a single argument"), cipaction->olong);
  513. namenode = findnamenode(filename, fnn_nonew);
  514. /* Print nothing if file is not diverted. */
  515. if (namenode == NULL)
  516. return 0;
  517. if (namenode->divert->pkg == NULL)
  518. /* Indicate package is local using something not in package
  519. * namespace. */
  520. printf("LOCAL\n");
  521. else
  522. printf("%s\n", namenode->divert->pkg->name);
  523. return 0;
  524. }
  525. static void
  526. setpackage(const struct cmdinfo *cip, const char *value)
  527. {
  528. opt_pkgname_match_any = false;
  529. /* If value is NULL we are being called from --local. */
  530. opt_pkgname = value;
  531. if (opt_pkgname && strchr(opt_pkgname, '\n') != NULL)
  532. badusage(_("package may not contain newlines"));
  533. }
  534. static void
  535. setdivertto(const struct cmdinfo *cip, const char *value)
  536. {
  537. opt_divertto = value;
  538. if (strchr(opt_divertto, '\n') != NULL)
  539. badusage(_("divert-to may not contain newlines"));
  540. }
  541. static const struct cmdinfo cmdinfo_add =
  542. ACTION("add", 0, 0, diversion_add);
  543. static const struct cmdinfo cmdinfos[] = {
  544. ACTION("add", 0, 0, diversion_add),
  545. ACTION("remove", 0, 0, diversion_remove),
  546. ACTION("list", 0, 0, diversion_list),
  547. ACTION("listpackage", 0, 0, diversion_listpackage),
  548. ACTION("truename", 0, 0, diversion_truename),
  549. { "admindir", 0, 1, NULL, &admindir, NULL },
  550. { "divert", 0, 1, NULL, NULL, setdivertto },
  551. { "package", 0, 1, NULL, NULL, setpackage },
  552. { "local", 0, 0, NULL, NULL, setpackage },
  553. { "quiet", 0, 0, &opt_verbose, NULL, NULL, 0 },
  554. { "rename", 0, 0, &opt_rename, NULL, NULL, 1 },
  555. { "test", 0, 0, &opt_test, NULL, NULL, 1 },
  556. { "help", 0, 0, NULL, NULL, usage },
  557. { "version", 0, 0, NULL, NULL, printversion },
  558. { NULL, 0 }
  559. };
  560. int
  561. main(int argc, const char * const *argv)
  562. {
  563. const char *env_pkgname;
  564. int (*actionfunction)(const char *const *argv);
  565. int ret;
  566. setlocale(LC_ALL, "");
  567. bindtextdomain(PACKAGE, LOCALEDIR);
  568. textdomain(PACKAGE);
  569. admindir = pkgadmindir_init(ADMINDIR);
  570. standard_startup();
  571. myopt(&argv, cmdinfos);
  572. env_pkgname = getenv("DPKG_MAINTSCRIPT_PACKAGE");
  573. if (!opt_pkgname && env_pkgname)
  574. setpackage(NULL, env_pkgname);
  575. if (!cipaction)
  576. setaction(&cmdinfo_add, NULL);
  577. actionfunction = (int (*)(const char *const *))cipaction->farg;
  578. setvbuf(stdout, NULL, _IONBF, 0);
  579. filesdbinit();
  580. ensure_diversions();
  581. ret = actionfunction(argv);
  582. standard_shutdown();
  583. return ret;
  584. }