README: review
[s-mailx.git] / quit.c
blob970c07d706788d9fe0d79f33936918a034e02e35
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Termination processing. TODO MBOX -> VFS; error handling: catastrophe!
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE quit
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 #include <utime.h>
45 enum quitflags {
46 QUITFLAG_HOLD = 1<<0,
47 QUITFLAG_KEEP = 1<<1,
48 QUITFLAG_KEEPSAVE = 1<<2,
49 QUITFLAG_APPEND = 1<<3
52 struct quitnames {
53 enum quitflags flag;
54 enum okeys okey;
57 static struct quitnames const _quitnames[] = {
58 {QUITFLAG_HOLD, ok_b_hold},
59 {QUITFLAG_KEEP, ok_b_keep},
60 {QUITFLAG_KEEPSAVE, ok_b_keepsave},
61 {QUITFLAG_APPEND, ok_b_append}
64 static char _mboxname[PATH_MAX]; /* Name of mbox */
66 /* Touch the indicated file */
67 static void _alter(char const *name);
69 /* Preserve all the appropriate messages back in the system mailbox, and print
70 * a nice message indicated how many were saved. On any error, just return -1.
71 * Else return 0. Incorporate the any new mail that we found */
72 static int writeback(FILE *res, FILE *obuf);
74 /* Terminate an editing session by attempting to write out the user's file from
75 * the temporary. Save any new stuff appended to the file */
76 static bool_t edstop(void);
78 static void
79 _alter(char const *name) /* TODO error handling */
81 #ifdef HAVE_UTIMENSAT
82 struct timespec tsa[2];
83 #else
84 struct stat sb;
85 struct utimbuf utb;
86 #endif
87 struct n_timespec const *tsp;
88 NYD_ENTER;
90 tsp = n_time_now(TRU1); /* TODO -> eventloop */
92 #ifdef HAVE_UTIMENSAT
93 tsa[0].tv_sec = tsp->ts_sec + 1;
94 tsa[0].tv_nsec = tsp->ts_nsec;
95 tsa[1].tv_nsec = UTIME_OMIT;
96 utimensat(AT_FDCWD, name, tsa, 0);
97 #else
98 if (!stat(name, &sb)) {
99 utb.actime = tsp->ts_sec;
100 utb.modtime = sb.st_mtime;
101 utime(name, &utb);
103 #endif
104 NYD_LEAVE;
107 static int
108 writeback(FILE *res, FILE *obuf) /* TODO errors */
110 struct message *mp;
111 int rv = -1, p, c;
112 NYD_ENTER;
114 if (fseek(obuf, 0L, SEEK_SET) == -1)
115 goto jleave;
117 srelax_hold();
118 for (p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
119 if ((mp->m_flag & MPRESERVE) || !(mp->m_flag & MTOUCH)) {
120 ++p;
121 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
122 n_perr(mailname, 0);
123 srelax_rele();
124 goto jerror;
126 srelax();
128 srelax_rele();
130 if(res != NULL){
131 bool_t lastnl;
133 for(lastnl = FAL0; (c = getc(res)) != EOF && putc(c, obuf) != EOF;)
134 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
135 if(lastnl != TRU2)
136 putc('\n', obuf);
138 ftrunc(obuf);
140 if (ferror(obuf)) {
141 n_perr(mailname, 0);
142 jerror:
143 fseek(obuf, 0L, SEEK_SET);
144 goto jleave;
146 if (fseek(obuf, 0L, SEEK_SET) == -1)
147 goto jleave;
149 _alter(mailname);
150 if (p == 1)
151 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
152 else
153 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
154 rv = 0;
155 jleave:
156 NYD_LEAVE;
157 return rv;
160 static bool_t
161 edstop(void) /* TODO oh my god */
163 int gotcha, c;
164 struct message *mp;
165 FILE *obuf = NULL, *ibuf = NULL;
166 struct stat statb;
167 enum n_fopen_state fs;
168 bool_t rv;
169 NYD_ENTER;
171 rv = TRU1;
173 if (mb.mb_perm == 0)
174 goto j_leave;
176 for (mp = message, gotcha = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
177 if (mp->m_flag & MNEW) {
178 mp->m_flag &= ~MNEW;
179 mp->m_flag |= MSTATUS;
181 if (mp->m_flag & (MODIFY | MDELETED | MSTATUS | MFLAG | MUNFLAG |
182 MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))
183 ++gotcha;
185 if (!gotcha)
186 goto jleave;
188 rv = FAL0;
190 /* TODO This is too simple minded? We should regenerate an index file
191 * TODO to be able to truly tell whether *anything* has changed!
192 * TODO (Or better: only come here.. then! It is an *object method!* */
193 /* TODO Ignoring stat error is easy, huh? */
194 if (!stat(mailname, &statb) && statb.st_size > mailsize) {
195 if ((obuf = Ftmp(NULL, "edstop", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
196 NULL) {
197 n_perr(_("tmpfile"), 0);
198 goto jleave;
200 if ((ibuf = n_fopen_any(mailname, "r", NULL)) == NULL) {
201 n_perr(mailname, 0);
202 goto jleave;
205 n_file_lock(fileno(ibuf), FLT_READ, 0,0, UIZ_MAX); /* TODO ign. lock err*/
206 fseek(ibuf, (long)mailsize, SEEK_SET);
207 while ((c = getc(ibuf)) != EOF) /* xxx bytewise??? TODO ... I/O error? */
208 putc(c, obuf);
209 Fclose(ibuf);
210 ibuf = obuf;
211 fflush_rewind(obuf);
212 /*obuf = NULL;*/
215 fprintf(n_stdout, _("%s "), n_shexp_quote_cp(displayname, FAL0));
216 fflush(n_stdout);
218 if ((obuf = n_fopen_any(mailname, "r+", &fs)) == NULL) {
219 int e = n_err_no;
220 n_perr(n_shexp_quote_cp(mailname, FAL0), e);
221 goto jleave;
223 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX); /* TODO ign. lock err! */
224 ftrunc(obuf);
226 srelax_hold();
227 c = 0;
228 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
229 if (mp->m_flag & MDELETED)
230 continue;
231 ++c;
232 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
233 srelax_rele();
234 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
235 goto jleave;
237 srelax();
239 srelax_rele();
241 gotcha = (c == 0 && ibuf == NULL);
242 if (ibuf != NULL) {
243 bool_t lastnl;
245 for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
246 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
247 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
248 putc('\n', obuf);
250 /* May nonetheless be a broken MBOX TODO really: VFS, object KNOWS!! */
251 else if(!gotcha && (fs & n_PROTO_MASK) == n_PROTO_FILE)
252 n_folder_mbox_prepare_append(obuf, NULL);
253 fflush(obuf);
254 if (ferror(obuf)) {
255 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
256 goto jleave;
259 if(gotcha){
260 /* Non-system boxes are never removed except forced via POSIX mode */
261 #ifdef HAVE_FTRUNCATE
262 ftruncate(fileno(obuf), 0);
263 #else
264 int fd;
266 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
267 0600)) != -1)
268 close(fd);
269 #endif
271 if(ok_blook(posix) && !ok_blook(keep) && n_path_rm(mailname))
272 fputs(_("removed\n"), n_stdout);
273 else
274 fputs(_("truncated\n"), n_stdout);
275 } else
276 fputs((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
277 ? _("complete\n") : _("updated.\n"), n_stdout);
278 fflush(n_stdout);
280 rv = TRU1;
281 jleave:
282 if (obuf != NULL)
283 Fclose(obuf);
284 if (ibuf != NULL)
285 Fclose(ibuf);
286 if(!rv){
287 /* TODO The codebase aborted by jumping to the main loop here.
288 * TODO The OpenBSD mailx simply ignores this error.
289 * TODO For now we follow the latter unless we are interactive,
290 * TODO in which case we ask the user whether the error is to be
291 * TODO ignored or not. More of this around here in this file! */
292 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
294 j_leave:
295 NYD_LEAVE;
296 return rv;
299 FL bool_t
300 quit(bool_t hold_sigs_on)
302 int p, modify, anystat, c;
303 FILE *fbuf, *lckfp, *rbuf, *abuf;
304 struct message *mp;
305 struct stat minfo;
306 bool_t rv;
307 NYD_ENTER;
309 if(!hold_sigs_on)
310 hold_sigs();
312 rv = FAL0;
313 fbuf = lckfp = rbuf = NULL;
314 if(mb.mb_digmsg != NULL)
315 n_dig_msg_on_mailbox_close(&mb);
316 temporary_folder_hook_unroll();
318 /* If we are read only, we can't do anything, so just return quickly */
319 /* TODO yet we cannot return quickly if resources have to be released!
320 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
321 *if (mb.mb_perm == 0)
322 * goto jleave;*/
323 p = (mb.mb_perm == 0);
325 switch (mb.mb_type) {
326 case MB_FILE:
327 break;
328 #ifdef HAVE_MAILDIR
329 case MB_MAILDIR:
330 rv = maildir_quit(TRU1);
331 goto jleave;
332 #endif
333 #ifdef HAVE_POP3
334 case MB_POP3:
335 rv = pop3_quit(TRU1);
336 goto jleave;
337 #endif
338 #ifdef HAVE_IMAP
339 case MB_IMAP:
340 case MB_CACHE:
341 rv = imap_quit(TRU1);
342 goto jleave;
343 #endif
344 case MB_VOID:
345 rv = TRU1;
346 /* FALLTHRU */
347 default:
348 goto jleave;
350 if (p) {
351 rv = TRU1;
352 goto jleave; /* TODO */
355 /* If editing (not reading system mail box), then do the work in edstop() */
356 if (n_pstate & n_PS_EDIT) {
357 rv = edstop();
358 goto jleave;
361 /* See if there any messages to save in mbox. If no, we
362 * can save copying mbox to /tmp and back.
364 * Check also to see if any files need to be preserved.
365 * Delete all untouched messages to keep them out of mbox.
366 * If all the messages are to be preserved, just exit with
367 * a message */
368 fbuf = n_fopen_any(mailname, "r+", NULL);
369 if (fbuf == NULL) {
370 if (n_err_no != n_ERR_NOENT)
371 jnewmail:
372 fprintf(n_stdout, _("Thou hast new mail.\n"));
373 rv = TRU1;
374 goto jleave;
377 if ((lckfp = n_dotlock(mailname, fileno(fbuf), FLT_WRITE, 0,0, UIZ_MAX)
378 ) == NULL) {
379 n_perr(_("Unable to (dot) lock mailbox"), 0);
380 Fclose(fbuf);
381 fbuf = NULL;
382 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
383 goto jleave;
386 rbuf = NULL;
387 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
388 bool_t lastnl;
390 fprintf(n_stdout, _("New mail has arrived.\n"));
391 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER);
392 if (rbuf == NULL || fbuf == NULL)
393 goto jnewmail;
394 fseek(fbuf, (long)mailsize, SEEK_SET);
395 for(lastnl = FAL0; (c = getc(fbuf)) != EOF && putc(c, rbuf) != EOF;)
396 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
397 if(lastnl != TRU2)
398 putc('\n', rbuf);
399 fflush_rewind(rbuf);
402 anystat = holdbits();
403 modify = 0;
404 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
405 if (mp->m_flag & MBOX)
406 c++;
407 if (mp->m_flag & MPRESERVE)
408 p++;
409 if (mp->m_flag & MODIFY)
410 modify++;
412 if (p == msgCount && !modify && !anystat) {
413 rv = TRU1;
414 if (p == 1)
415 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
416 else if (p > 1)
417 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
418 goto jleave;
421 if (c == 0) {
422 if (p != 0) {
423 if (writeback(rbuf, fbuf) >= 0)
424 rv = TRU1;
425 else
426 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
427 goto jleave;
429 goto jcream;
432 if (makembox() == STOP) {
433 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
434 goto jleave;
437 /* Now we are ready to copy back preserved files to the system mailbox, if
438 * any were requested */
439 if (p != 0) {
440 if (writeback(rbuf, fbuf) < 0)
441 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
442 goto jleave;
445 /* Finally, remove his file. If new mail has arrived, copy it back */
446 jcream:
447 if (rbuf != NULL) {
448 abuf = fbuf;
449 fseek(abuf, 0L, SEEK_SET);
450 while ((c = getc(rbuf)) != EOF)
451 putc(c, abuf);
452 ftrunc(abuf);
453 _alter(mailname);
454 rv = TRU1;
455 } else {
456 #ifdef HAVE_FTRUNCATE
457 ftruncate(fileno(fbuf), 0);
458 #else
459 int fd;
461 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
462 0600)) != -1)
463 close(fd);
464 #endif
465 if(!ok_blook(keep))
466 n_path_rm(mailname);
467 rv = TRU1;
469 jleave:
470 if(rbuf != NULL)
471 Fclose(rbuf);
472 if (fbuf != NULL) {
473 Fclose(fbuf);
474 if (lckfp != NULL && lckfp != (FILE*)-1)
475 Pclose(lckfp, FAL0);
478 if(!hold_sigs_on)
479 rele_sigs();
480 NYD_LEAVE;
481 return rv;
484 FL int
485 holdbits(void)
487 struct message *mp;
488 int anystat, autohold, holdbit, nohold;
489 NYD_ENTER;
491 anystat = 0;
492 autohold = ok_blook(hold);
493 holdbit = autohold ? MPRESERVE : MBOX;
494 nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
495 if (ok_blook(keepsave))
496 nohold &= ~MSAVED;
497 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
498 if (mp->m_flag & MNEW) {
499 mp->m_flag &= ~MNEW;
500 mp->m_flag |= MSTATUS;
502 if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
503 MDRAFT | MUNDRAFT))
504 ++anystat;
505 if (!(mp->m_flag & MTOUCH))
506 mp->m_flag |= MPRESERVE;
507 if (!(mp->m_flag & nohold))
508 mp->m_flag |= holdbit;
510 NYD_LEAVE;
511 return anystat;
514 FL enum okay
515 makembox(void) /* TODO oh my god (also error reporting) */
517 struct message *mp;
518 char *mbox, *tempQuit;
519 int mcount, c;
520 FILE *ibuf = NULL, *obuf, *abuf;
521 enum n_fopen_state fs;
522 enum okay rv = STOP;
523 NYD_ENTER;
525 mbox = _mboxname;
526 mcount = 0;
527 if (ok_blook(append)) {
528 if ((obuf = n_fopen_any(mbox, "a+", &fs)) == NULL) {
529 n_perr(mbox, 0);
530 goto jleave;
532 if((fs & n_PROTO_MASK) == n_PROTO_FILE)
533 n_folder_mbox_prepare_append(obuf, NULL);
534 } else {
535 if ((obuf = Ftmp(&tempQuit, "makembox",
536 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER)) == NULL) {
537 n_perr(_("temporary mail quit file"), 0);
538 goto jleave;
540 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
541 n_perr(tempQuit, 0);
542 Ftmp_release(&tempQuit);
543 if (ibuf == NULL) {
544 Fclose(obuf);
545 goto jleave;
548 if ((abuf = n_fopen_any(mbox, "r", &fs)) != NULL) {
549 bool_t lastnl;
551 for (lastnl = FAL0; (c = getc(abuf)) != EOF && putc(c, obuf) != EOF;)
552 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
553 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
554 putc('\n', obuf);
556 Fclose(abuf);
558 if (ferror(obuf)) {
559 n_perr(_("temporary mail quit file"), 0);
560 Fclose(ibuf);
561 Fclose(obuf);
562 goto jleave;
564 Fclose(obuf);
566 if ((c = open(mbox, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
567 0666)) != -1)
568 close(c);
569 if ((obuf = n_fopen_any(mbox, "r+", &fs)) == NULL) {
570 n_perr(mbox, 0);
571 Fclose(ibuf);
572 goto jleave;
576 srelax_hold();
577 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
578 if (mp->m_flag & MBOX) {
579 ++mcount;
580 #ifdef HAVE_IMAP
581 if((fs & n_PROTO_MASK) == n_PROTO_IMAP &&
582 !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(mbox)){
583 if(imap_copy(mp, PTR2SIZE(mp - message + 1), mbox) == STOP)
584 goto jcopyerr;
585 }else
586 #endif
587 if (sendmp(mp, obuf, n_IGNORE_SAVE, NULL, SEND_MBOX, NULL) < 0) {
588 #ifdef HAVE_IMAP
589 jcopyerr:
590 #endif
591 n_perr(mbox, 0);
592 srelax_rele();
593 if (ibuf != NULL)
594 Fclose(ibuf);
595 Fclose(obuf);
596 goto jleave;
598 mp->m_flag |= MBOXED;
599 srelax();
602 srelax_rele();
604 /* Copy the user's old mbox contents back to the end of the stuff we just
605 * saved. If we are appending, this is unnecessary */
606 if (!ok_blook(append)) {
607 bool_t lastnl;
609 rewind(ibuf);
610 for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
611 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
612 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
613 putc('\n', obuf);
614 Fclose(ibuf);
615 fflush(obuf);
617 ftrunc(obuf);
618 if (ferror(obuf)) {
619 n_perr(mbox, 0);
620 Fclose(obuf);
621 goto jleave;
623 if (Fclose(obuf) != 0) {
624 #ifdef HAVE_IMAP
625 if((fs & n_PROTO_MASK) != n_PROTO_IMAP)
626 #endif
627 n_perr(mbox, 0);
628 goto jleave;
630 if (mcount == 1)
631 fprintf(n_stdout, _("Saved 1 message in mbox\n"));
632 else
633 fprintf(n_stdout, _("Saved %d messages in mbox\n"), mcount);
634 rv = OKAY;
635 jleave:
636 NYD_LEAVE;
637 return rv;
640 FL void
641 save_mbox_for_possible_quitstuff(void){ /* TODO try to get rid of that */
642 char const *cp;
643 NYD2_ENTER;
645 if((cp = fexpand("&", FEXP_NVAR)) == NULL)
646 cp = n_empty;
647 n_strscpy(_mboxname, cp, sizeof _mboxname);
648 NYD2_LEAVE;
651 FL int
652 savequitflags(void)
654 enum quitflags qf = 0;
655 size_t i;
656 NYD_ENTER;
658 for (i = 0; i < n_NELEM(_quitnames); ++i)
659 if (n_var_oklook(_quitnames[i].okey) != NULL)
660 qf |= _quitnames[i].flag;
661 NYD_LEAVE;
662 return qf;
665 FL void
666 restorequitflags(int qf)
668 size_t i;
669 NYD_ENTER;
671 for (i = 0; i < n_NELEM(_quitnames); ++i) {
672 char *x = n_var_oklook(_quitnames[i].okey);
673 if (qf & _quitnames[i].flag) {
674 if (x == NULL)
675 n_var_okset(_quitnames[i].okey, TRU1);
676 } else if (x != NULL)
677 n_var_okclear(_quitnames[i].okey);
679 NYD_LEAVE;
682 /* s-it-mode */