we’ll need to distinguish these for sarge/etch as well
[alioth/jupp.git] / ufile.c
1 /*
2  *      User file operations
3  *      Copyright
4  *              (C) 1992 Joseph H. Allen
5  *
6  *      This file is part of JOE (Joe's Own Editor)
7  */
8 #define EXTERN_UFILE_C
9 #include "config.h"
10 #include "types.h"
11
12 __RCSID("$MirOS: contrib/code/jupp/ufile.c,v 1.14 2017/12/02 04:32:42 tg Exp $");
13
14 #include <unistd.h>
15 #ifdef HAVE_SYS_STAT_H
16 #include <sys/stat.h>
17 #endif
18 #ifdef HAVE_FCNTL_H
19 #include <fcntl.h>
20 #endif
21 #ifdef HAVE_STDLIB_H
22 #include <stdlib.h>
23 #endif
24
25 #ifdef UTIME
26 #include <utime.h>
27 #define HAVEUTIME 1
28 #else
29 #ifdef SYSUTIME
30 #include <sys/utime.h>
31 #define HAVEUTIME 1
32 #endif
33 #endif
34
35 #ifdef WITH_SELINUX
36 int copy_security_context(const char *from_file, const char *to_file);
37 #endif
38
39 #include "b.h"
40 #include "bw.h"
41 #include "macro.h"
42 #include "main.h"
43 #include "menu.h"
44 #include "path.h"
45 #include "pw.h"
46 #include "qw.h"
47 #include "scrn.h"
48 #include "tab.h"
49 #include "tty.h"
50 #include "tw.h"
51 #include "ublock.h"
52 #include "uerror.h"
53 #include "ufile.h"
54 #include "ushell.h"
55 #include "utils.h"
56 #include "va.h"
57 #include "vs.h"
58 #include "charmap.h"
59 #include "w.h"
60
61 extern int orphan;
62 unsigned char *backpath = NULL;         /* Place to store backup files */
63 static B *filehist = NULL;      /* History of file names */
64 int nobackups = 0;
65 int exask = 0;
66
67 /* Ending message generator */
68 /**** message which is shown after closing joe (CTRL+x; CTRL+k) *****/
69 void genexmsg(BW *bw, int saved, unsigned char *name)
70 {
71         unsigned char *s;
72
73         if (bw->b->name && bw->b->name[0]) {
74                 s = bw->b->name;
75         } else {
76                 s = US "(Unnamed)";
77         }
78
79         if (name) {
80                 if (saved) {
81                         joe_snprintf_1((char *)msgbuf, JOE_MSGBUFSIZE, "File %s saved", name);
82                 } else {
83                         joe_snprintf_1((char *)msgbuf, JOE_MSGBUFSIZE, "File %s not saved", name);
84                 }
85         } else if (bw->b->changed && bw->b->count == 1) {
86                 joe_snprintf_1((char *)msgbuf, JOE_MSGBUFSIZE, "File %s not saved", s);
87         } else if (saved) {
88                 joe_snprintf_1((char *)msgbuf, JOE_MSGBUFSIZE, "File %s saved", s);
89         } else {
90                 joe_snprintf_1((char *)msgbuf, JOE_MSGBUFSIZE, "File %s not changed so no update needed", s);
91         }
92         msgnw(bw->parent, msgbuf);
93
94         if (exmsg)
95                 vsrm(exmsg);
96
97         if (bw->b->changed && bw->b->count == 1) {
98                 exmsg = vsncpy(NULL, 0, sc("File "));
99                 exmsg = vsncpy(sv(exmsg), sz(s));
100                 exmsg = vsncpy(sv(exmsg), sc(" not saved."));
101         } else if (saved) {
102                 exmsg = vsncpy(NULL, 0, sc("File "));
103                 exmsg = vsncpy(sv(exmsg), sz(s));
104                 exmsg = vsncpy(sv(exmsg), sc(" saved."));
105         } else {
106                 exmsg = vsncpy(NULL, 0, sc("File "));
107                 exmsg = vsncpy(sv(exmsg), sz(s));
108                 exmsg = vsncpy(sv(exmsg), sc(" not changed so no update needed."));
109         }
110 }
111
112 /* For ^X ^C */
113 void genexmsgmulti(BW *bw, int saved, int skipped)
114 {
115         if (saved)
116                 if (skipped)
117                         joe_snprintf_0((char *)msgbuf, JOE_MSGBUFSIZE, "Some files have not been saved.");
118                 else
119                         joe_snprintf_0((char *)msgbuf, JOE_MSGBUFSIZE, "All modified files have been saved.");
120         else
121                 joe_snprintf_0((char *)msgbuf, JOE_MSGBUFSIZE, "No modified files, so no updates needed.");
122
123         msgnw(bw->parent, msgbuf);
124
125         exmsg = vsncpy(NULL,0,sz(msgbuf));
126 }
127
128 /* Write highlighted block to a file */
129
130 int ublksave(BW *bw)
131 {
132         if (markb && markk && markb->b == markk->b && (markk->byte - markb->byte) > 0 && (!square || piscol(markk) > piscol(markb))) {
133                 if (wmkpw(bw->parent, US "Name of file to write (^C to abort): ", &filehist, dowrite, US "Names", NULL, cmplt, NULL, NULL, locale_map)) {
134                         return 0;
135                 } else {
136                         return -1;
137                 }
138         } else {
139                 return usave(bw);
140         }
141 }
142
143 /* Shell escape */
144
145 int ushell(BW *bw)
146 {
147         nescape(bw->parent->t->t);
148         ttsusp();
149         nreturn(bw->parent->t->t);
150         return 0;
151 }
152
153 /* Copy a file */
154
155 static int
156 cp(unsigned char *from, int g, unsigned char *tmpfn, unsigned char *to)
157 {
158         int f, amnt;
159         struct stat sbuf;
160
161 #ifdef HAVEUTIME
162 #ifdef NeXT
163         time_t utbuf[2];
164 #else
165         struct utimbuf utbuf;
166 #endif
167 #endif
168
169         f = open((char *)from, O_RDONLY);
170         if (f < 0) {
171                 return -1;
172         }
173         if (fstat(f, &sbuf) < 0) {
174                 return -1;
175         }
176         if (fchmod(g, sbuf.st_mode & 0777)) {
177                 close(f);
178                 return (-1);
179         }
180         while ((amnt = read(f, stdbuf, stdsiz)) > 0) {
181                 if (amnt != joe_write(g, stdbuf, amnt)) {
182                         break;
183                 }
184         }
185         close(f);
186         if (amnt) {
187                 return -1;
188         }
189
190         if (tmpfn && rename(tmpfn, to)) {
191                 return (-1);
192         }
193         /*
194          * Do not return !0 from here on.
195          * Below are only operations that run when the copy
196          * process finished successfully.
197          */
198         close(g);
199
200 #ifdef HAVEUTIME
201 #ifdef NeXT
202         utbuf[0] = (time_t) sbuf.st_atime;
203         utbuf[1] = (time_t) sbuf.st_mtime;
204 #else
205         utbuf.actime = sbuf.st_atime;
206         utbuf.modtime = sbuf.st_mtime;
207 #endif
208         utime(to, &utbuf);
209 #endif
210
211 #ifdef WITH_SELINUX
212         copy_security_context(from, to);
213 #endif
214
215         return 0;
216 }
217
218 /* Make backup file if it needs to be made
219  * Returns 0 if backup file was made or didn't need to be made
220  * Returns 1 for error
221  */
222
223 static int
224 backup(BW *bw)
225 {
226         unsigned char tmp[1024 + 12];
227         unsigned char name[1024];
228         unsigned char *simple_backup_suffix;
229         int fd;
230
231         if (bw->b->backup || nobackups || !(bw->b->name) || !(bw->b->name[0]))
232                 return (0);
233
234         /* Create backup file name */
235         simple_backup_suffix = (unsigned char *)getenv("SIMPLE_BACKUP_SUFFIX");
236
237         if (simple_backup_suffix == NULL) {
238                 simple_backup_suffix = US "~";
239         }
240         if (backpath) {
241                 joe_snprintf_3((char *)name, sizeof(name), "%s/%s%s", backpath, namepart(tmp, bw->b->name), simple_backup_suffix);
242         } else {
243                 joe_snprintf_2((char *)name, sizeof(name), "%s%s", bw->b->name, simple_backup_suffix);
244         }
245
246         /* Securely generate a backup file temporary file */
247         *tmp = '\0';
248         if (*name != '/') {
249                 /* relative pathname */
250                 if (!getcwd((char *)tmp, sizeof(tmp)) ||
251                     strlcat((char *)tmp, "/", sizeof(tmp)) >= sizeof(tmp))
252                         return (1);
253         }
254         if (strlcat((char *)tmp, (char *)name, sizeof(tmp)) >= sizeof(tmp))
255                 return (1);
256         *(dirprt_ptr(tmp)) = '\0';
257         if ((simple_backup_suffix = mktmp(tmp, &fd)) == NULL)
258                 return (1);
259
260         /* Attempt to delete backup file first */
261         unlink((char *)name);
262
263         /* Copy original file to backup file securely */
264         if (cp(bw->b->name, fd, simple_backup_suffix, name)) {
265                 close(fd);
266                 unlink((char *)simple_backup_suffix);
267                 return (1);
268         }
269
270         bw->b->backup = 1;
271         return (0);
272 }
273
274 /* Write file */
275
276 /* Continuation structure */
277
278 struct savereq {
279         int (*callback) ();
280         unsigned char *name;
281         B *first;
282         int not_saved;  /* Set if a modified file was not saved */
283         int rename;     /* Set if we're renaming the file during save */
284 };
285
286 struct savereq *mksavereq(int (*callback)(), unsigned char *name, B *first,int rename_)
287 {
288         struct savereq *req = (struct savereq *) joe_malloc(sizeof(struct savereq));
289         req->callback = callback;
290         req->name = name;
291         req->first = first;
292         req->not_saved = 0;
293         req->rename = rename_;
294         return req;
295 }
296
297 static void rmsavereq(struct savereq *req)
298 {
299         vsrm(req->name);
300         joe_free(req);
301 }
302
303 static int saver(BW *bw, int c, struct savereq *req, int *notify)
304 {
305         int fl;
306         if (c == 'n' || c == 'N') {
307                 msgnw(bw->parent, US "Couldn't make backup file... file not saved");
308                 if (req->callback) {
309                         return req->callback(bw, req, -1, notify);
310                 } else {
311                         if (notify) {
312                                 *notify = 1;
313                         }
314                         rmsavereq(req);
315                         return -1;
316                 }
317         }
318         if (c != 'y' && c != 'Y') {
319                 if (mkqw(bw->parent, sc("Could not make backup file.  Save anyway (y,n,^C)? "), saver, NULL, req, notify)) {
320                         return 0;
321                 } else {
322                         rmsavereq(req);
323                         if (notify)
324                                 *notify = 1;
325                         return -1;
326                 }
327         }
328         if (bw->b->er == -1 && bw->o.msnew) {
329                 exemac(bw->o.msnew);
330                 bw->b->er = -3;
331         }
332         if (bw->b->er == 0 && bw->o.msold) {
333                 exemac(bw->o.msold);
334         }
335         if ((fl = bsave(bw->b->bof, req->name, bw->b->eof->byte, 1)) != 0) {
336                 msgnw(bw->parent, msgs[-fl]);
337                 if (req->callback) {
338                         return req->callback(bw, req, -1, notify);
339                 } else {
340                         rmsavereq(req);
341                         if (notify) {
342                                 *notify = 1;
343                         }
344                         return -1;
345                 }
346         } else {
347                 if (req->rename) {
348                         joe_free(bw->b->name);
349                         bw->b->name = 0;
350                 }
351                 if (!bw->b->name)
352                         bw->b->name = joesep((unsigned char *)strdup((char *)req->name));
353                 if (!strcmp(bw->b->name, req->name)) {
354                         bw->b->changed = 0;
355                         saverr(bw->b->name);
356                 }
357                 {
358                         /* Last UNDOREC which wasn't modified will be changed
359                          * to modified. And because this block is
360                          * executed after each 'save', there can't be more
361                          * than one record which is not modified
362                          *              24 Apr 2001, Marx
363                          */
364                         UNDO *u = bw->b->undo;
365                         UNDOREC *rec, *rec_start;
366
367                         rec = rec_start = &u->recs;
368
369                         do {
370                                 rec = rec->link.prev;
371                         } while (rec != rec_start && rec->changed);
372                         if(rec->changed == 0)
373                                 rec->changed = 1;
374
375                 }
376                 genexmsg(bw, 1, req->name);
377                 if (req->callback) {
378                         return req->callback(bw, req, 0, notify);
379                 } else {
380                         rmsavereq(req);
381                         return 0;
382                 }
383         }
384 }
385
386 static int dosave(BW *bw, struct savereq *req, int *notify)
387 {
388         if (backup(bw)) {
389                 return saver(bw, 0, req, notify);
390         } else {
391                 return saver(bw, 'y', req, notify);
392         }
393 }
394
395 static int dosave2(BW *bw, int c, struct savereq *req, int *notify)
396 {
397         if (c == 'y' || c == 'Y') {
398                 return dosave(bw, req, notify);
399         } else if (c == 'n' || c == 'N') {
400                 if (notify) {
401                         *notify = 1;
402                 }
403                 genexmsg(bw, 0, req->name);
404                 rmsavereq(req);
405                 return -1;
406         } else if (mkqw(bw->parent, sc("File exists.  Overwrite (y,n,^C)? "), dosave2, NULL, req, notify)) {
407                 return 0;
408         } else {
409                 /* Should be in abort function */
410                 rmsavereq(req);
411                 return -1;
412         }
413 }
414
415 static int dosave2a(BW *bw, int c, struct savereq *req, int *notify)
416 {
417         if (c == 'y' || c == 'Y') {
418                 return dosave(bw, req, notify);
419         } else if (c == 'n' || c == 'N') {
420                 if (notify) {
421                         *notify = 1;
422                 }
423                 genexmsg(bw, 0, req->name);
424                 rmsavereq(req);
425                 return -1;
426         } else if (mkqw(bw->parent, sc("File on disk is newer.  Overwrite (y,n,^C)? "), dosave2a, NULL, req, notify)) {
427                 return 0;
428         } else {
429                 rmsavereq(req);
430                 return -1;
431         }
432 }
433
434 /* Checks if file exists. */
435
436 static int dosave1(BW *bw, unsigned char *s, struct savereq *req, int *notify)
437 {
438         int f;
439
440         if (req->name)
441                 vsrm(req->name);
442         req->name = s;
443
444         if (s[0] != '!' && !(s[0] == '>' && s[1] == '>')) {
445                 /* It's a normal file: not a pipe or append */
446                 if (!bw->b->name || strcmp(s, bw->b->name)) {
447                         /* Newly named file or name is different than buffer */
448                         f = open((char *)s, O_RDONLY);
449                         if (f != -1) {
450                                 close(f);
451                                 return dosave2(bw, 0, req, notify);
452                         }
453                 }
454                 else {
455                         /* We're saving a newer version of the same file */
456                         struct stat sbuf;
457                         if (!stat((char *)s,&sbuf)) {
458                                 if (sbuf.st_mtime>bw->b->mod_time) {
459                                         return dosave2a(bw, 0, req, notify);
460                                 }
461                         }
462                 }
463         }
464
465         return dosave(bw, req, notify);
466 }
467
468 /* User command: ^K D */
469
470 int usave(BW *bw)
471 {
472         BW *pbw;
473
474         pbw = wmkpw(bw->parent, US "Name of file to save (^C to abort): ", &filehist, dosave1, US "Names", NULL, cmplt, mksavereq(NULL,NULL,NULL,0), NULL, locale_map);
475
476         if (pbw && bw->b->name) {
477                 binss(pbw->cursor, bw->b->name);
478                 pset(pbw->cursor, pbw->b->eof);
479                 pbw->cursor->xcol = piscol(pbw->cursor);
480         }
481         if (pbw) {
482                 return 0;
483         } else {
484                 return -1;
485         }
486 }
487
488 /* Load file to edit */
489
490 int doedit1(BW *bw,int c,unsigned char *s,int *notify)
491 {
492         int ret = 0;
493         int er;
494         void *object;
495         W *w;
496         B *b;
497         if (c=='y' || c=='Y') {
498                 /* Reload from file */
499
500                 if (notify) {
501                         *notify = 1;
502                 }
503
504                 b = bfind_reload(s);
505                 er = error;
506                 if (bw->b->count >= 1 && (bw->b->changed || bw->b->name)) {
507                         if (orphan) {
508                                 orphit(bw);
509                         } else {
510                                 if (uduptw(bw)) {
511                                         brm(b);
512                                         return -1;
513                                 }
514                                 bw = (BW *) maint->curwin->object;
515                         }
516                 }
517                 if (er) {
518                         msgnwt(bw->parent, msgs[-er]);
519                         if (er != -1) {
520                                 ret = -1;
521                         }
522                 }
523                 object = bw->object;
524                 w = bw->parent;
525                 bwrm(bw);
526                 w->object = (void *) (bw = bwmk(w, b, 0));
527                 wredraw(bw->parent);
528                 bw->object = object;
529                 vsrm(s);
530                 if (er == -1 && bw->o.mnew) {
531                         exemac(bw->o.mnew);
532                 }
533                 if (er == 0 && bw->o.mold) {
534                         exemac(bw->o.mold);
535                 }
536                 return ret;
537         } else if(c=='n' || c=='N') {
538                 /* Edit already loaded buffer */
539
540                 if (notify) {
541                         *notify = 1;
542                 }
543
544                 b = bfind(s);
545                 er = error;
546                 if (bw->b->count == 1 && (bw->b->changed || bw->b->name)) {
547                         if (orphan) {
548                                 orphit(bw);
549                         } else {
550                                 if (uduptw(bw)) {
551                                         brm(b);
552                                         return -1;
553                                 }
554                                 bw = (BW *) maint->curwin->object;
555                         }
556                 }
557                 if (er) {
558                         msgnwt(bw->parent, msgs[-er]);
559                         if (er != -1) {
560                                 ret = -1;
561                         }
562                 }
563                 object = bw->object;
564                 w = bw->parent;
565                 bwrm(bw);
566                 w->object = (void *) (bw = bwmk(w, b, 0));
567                 wredraw(bw->parent);
568                 bw->object = object;
569                 vsrm(s);
570                 if (er == -1 && bw->o.mnew) {
571                         exemac(bw->o.mnew);
572                 }
573                 if (er == 0 && bw->o.mold) {
574                         exemac(bw->o.mold);
575                 }
576                 return ret;
577         } else {
578                 /* FIXME: need abort handler to prevent leak */
579                 if (mkqw(bw->parent, sc("Load original file from disk (y,n,^C)? "), doedit1, NULL, s, notify))
580                         return 0;
581                 else {
582                         vsrm(s);
583                         return -1;
584                 }
585         }
586 }
587
588 int doedit(BW *bw, unsigned char *s, void *obj, int *notify)
589 {
590         B *b;
591
592         b = bcheck_loaded(s);
593
594         if (b) {
595                 if (b->changed)
596                         /* Modified buffer exists, so ask */
597                         return doedit1(bw, 0, s, notify);
598                 else
599                         /* Buffer not modified- just use it as is */
600                         return doedit1(bw, 'n', s, notify);
601         } else
602                 /* File not in buffer: don't ask */
603                 return doedit1(bw, 'y', s, notify);
604 }
605
606 int okrepl(BW *bw)
607 {
608         if (bw->b->count == 1 && bw->b->changed) {
609                 msgnw(bw->parent, US "Can't replace modified file");
610                 return -1;
611         } else {
612                 return 0;
613         }
614 }
615
616 int uedit(BW *bw)
617 {
618         if (wmkpw(bw->parent, US "Name of file to edit (^C to abort): ", &filehist, doedit, US "Names", NULL, cmplt, NULL, NULL, locale_map)) {
619                 return 0;
620         } else {
621                 return -1;
622         }
623 }
624
625 int doswitch(BW *bw, unsigned char *s, void *obj, int *notify)
626 {
627         /* Try buffer, then file */
628         return doedit1(bw, 'n', s, notify);
629 }
630
631 int uswitch(BW *bw)
632 {
633         if (wmkpw(bw->parent, US "Name of buffer to edit (^C to abort): ", &filehist, doswitch, US "Names", NULL, cmplt, NULL, NULL, locale_map)) {
634                 return 0;
635         } else {
636                 return -1;
637         }
638 }
639
640 int doscratch(BW *bw, unsigned char *s, void *obj, int *notify)
641 {
642         int ret = 0;
643         int er;
644         void *object;
645         W *w;
646         B *b;
647
648         if (notify) {
649                 *notify = 1;
650         }
651
652         b = bfind_scratch(s);
653         er = error;
654         if (bw->b->count == 1 && (bw->b->changed || bw->b->name)) {
655                 if (orphan) {
656                         orphit(bw);
657                 } else {
658                         if (uduptw(bw)) {
659                                 brm(b);
660                                 return -1;
661                         }
662                         bw = (BW *) maint->curwin->object;
663                 }
664         }
665         if (er && er != -1) {
666                 msgnwt(bw->parent, msgs[-er]);
667                 ret = -1;
668         }
669         object = bw->object;
670         w = bw->parent;
671         bwrm(bw);
672         w->object = (void *) (bw = bwmk(w, b, 0));
673         wredraw(bw->parent);
674         bw->object = object;
675         vsrm(s);
676         if (er == -1 && bw->o.mnew) {
677                 exemac(bw->o.mnew);
678         }
679         if (er == 0 && bw->o.mold) {
680                 exemac(bw->o.mold);
681         }
682         return ret;
683 }
684
685 int uscratch(BW *bw)
686 {
687         if (wmkpw(bw->parent, US "Name of scratch buffer to edit (^C to abort): ", &filehist, doscratch, US "Names", NULL, cmplt, NULL, NULL, locale_map)) {
688                 return 0;
689         } else {
690                 return -1;
691         }
692 }
693
694 /* Load file into buffer: can result in an orphaned buffer */
695
696 static int dorepl(BW *bw, unsigned char *s, void *obj, int *notify)
697 {
698         void *object = bw->object;
699         int ret = 0;
700         int er;
701         W *w = bw->parent;
702         B *b;
703
704         if (notify) {
705                 *notify = 1;
706         }
707         b = bfind(s);
708         er = error;
709         if (error) {
710                 msgnwt(bw->parent, msgs[-error]);
711                 if (error != -1) {
712                         ret = -1;
713                 }
714         }
715         if (bw->b->count == 1 && (bw->b->changed || bw->b->name)) {
716                 orphit(bw);
717         }
718         bwrm(bw);
719         w->object = (void *) (bw = bwmk(w, b, 0));
720         wredraw(bw->parent);
721         bw->object = object;
722         vsrm(s);
723         if (er == -1 && bw->o.mnew) {
724                 exemac(bw->o.mnew);
725         }
726         if (er == 0 && bw->o.mold) {
727                 exemac(bw->o.mold);
728         }
729         return ret;
730 }
731
732 /* Switch to next buffer in window */
733
734 int unbuf(BW *bw)
735 {
736         void *object = bw->object;
737         W *w = bw->parent;
738         B *b;
739         b = bnext();
740         if (b == bw->b) {
741                 b = bnext();
742         }
743         if (b == bw->b) {
744                 return 0;
745                 /* return -1; this helps with querysave (no error when only one buffer) */
746         }
747         if (!b->orphan) {
748                 ++b->count;
749         } else {
750                 b->orphan = 0;
751         }
752         if (bw->b->count == 1) {
753                 orphit(bw);
754         }
755         bwrm(bw);
756         w->object = (void *) (bw = bwmk(w, b, 0));
757         wredraw(bw->parent);
758         bw->object = object;
759         return 0;
760 }
761
762 int upbuf(BW *bw)
763 {
764         void *object = bw->object;
765         W *w = bw->parent;
766         B *b;
767         b = bprev();
768         if (b == bw->b) {
769                 b = bprev();
770         }
771         if (b == bw->b) {
772                 return 0;
773                 /* return -1; */
774         }
775         if (!b->orphan) {
776                 ++b->count;
777         } else {
778                 b->orphan = 0;
779         }
780         if (bw->b->count == 1) {
781                 orphit(bw);
782         }
783         bwrm(bw);
784         w->object = (void *) (bw = bwmk(w, b, 0));
785         wredraw(bw->parent);
786         bw->object = object;
787         return 0;
788 }
789
790 int uinsf(BW *bw)
791 {
792         if (wmkpw(bw->parent, US "Name of file to insert (^C to abort): ", &filehist, doinsf, US "Names", NULL, cmplt, NULL, NULL, locale_map)) {
793                 return 0;
794         } else {
795                 return -1;
796         }
797 }
798
799 /* Save and exit */
800
801 static int exdone(BW *bw, struct savereq *req,int flg,int *notify)
802 {
803         if (notify)
804                 *notify = 1;
805         rmsavereq(req);
806         if (flg) {
807                 return -1;
808         } else {
809                 bw->b->changed = 0;
810                 saverr(bw->b->name);
811                 return uabort1(bw, -1);
812         }
813 }
814
815 int uexsve(BW *bw)
816 {
817         if (!bw->b->changed || bw->b->scratch) {
818                 /* It didn't change or it's just a scratch buffer: don't save */
819                 uabort(bw, -1);
820                 return 0;
821         } else if (bw->b->name && !exask) {
822                 /* It changed, it's not a scratch buffer and it's named */
823                 return dosave1(bw, vsncpy(NULL, 0, sz(bw->b->name)), mksavereq(exdone,NULL,NULL,0), NULL);
824         } else {
825                 BW *pbw = wmkpw(bw->parent, US "Name of file to save (^C to abort): ", &filehist, dosave1, US "Names", NULL, cmplt, mksavereq(exdone,NULL,NULL,1), NULL, locale_map);
826
827                 if (pbw && bw->b->name) {
828                         binss(pbw->cursor, bw->b->name);
829                         pset(pbw->cursor, pbw->b->eof);
830                         pbw->cursor->xcol = piscol(pbw->cursor);
831                 }
832                 if (pbw) {
833                         return 0;
834                 } else {
835                         return -1;
836                 }
837         }
838 }
839
840 /* If buffer is modified, prompt for saving: if user types 'n', uabort(), otherwise just return. */
841 /* If buffer is not modified, just return. */
842
843 static int nask(BW *bw, int c, void *object, int *notify)
844 {
845         if (c == 'y' || c == 'Y') {
846                 /* uexsve macro should be here... */
847                 if(notify)
848                         *notify = 1;
849                 return 0;
850         } else if (c == 'n' || c == 'N') {
851                 if(notify)
852                         *notify = -1;
853                 genexmsg(bw, 0, NULL);
854                 abortit(bw);
855                 return -1;
856         } else if (bw->b->count == 1 && bw->b->changed && !bw->b->scratch) {
857                 if (mkqw(bw->parent, sc("Save changes to this file (y,n,^C)? "), nask, NULL, object, notify)) {
858                         return 0;
859                 } else {
860                         return -1;
861                 }
862         } else {
863                 if (notify) {
864                         *notify = 1;
865                 }
866                 return 0;
867         }
868 }
869
870 int uask(BW *bw)
871 {
872         return nask(bw, 0, NULL, NULL);
873 }
874
875 /* Kill a buffer: any windows which have it get their buffer replaced with a
876  * a scratch buffer */
877
878 static int dolose(BW *bw, int c, void *object, int *notify)
879 {
880         W *w;
881         B *b, *new_b;
882         int cnt;
883
884         if (notify) {
885                 *notify = 1;
886         }
887         if (c != 'y' && c != 'Y') {
888                 return -1;
889         }
890
891         b=bw->b;
892         cnt = b->count;
893         b->count = 1;
894         genexmsg(bw, 0, NULL);
895         b->count = cnt;
896
897         if ((w = maint->topwin) != NULL) {
898                 do {
899                         if ((w->watom->what&TYPETW) && ((BW *)w->object)->b==b) {
900                                 if ((new_b = borphan()) != NULL) {
901                                         BW *bw_ = (BW *)w->object;
902                                         void *object_ = bw_->object;
903                                         /* FIXME: Shouldn't we wabort() and wcreate here to kill
904                                            any prompt windows? */
905
906                                         bwrm(bw_);
907                                         w->object = (void *) (bw_ = bwmk(w, new_b, 0));
908                                         wredraw(w);
909                                         bw_->object = object_;
910                                 } else {
911                                         BW *bw_ = (BW *)w->object;
912                                         object = bw_->object;
913                                         bwrm(bw_);
914                                         w->object = (void *) (bw_ = bwmk(w, bfind(US ""), 0));
915                                         wredraw(w);
916                                         bw_->object = object;
917                                         if (bw_->o.mnew)
918                                                 exemac(bw_->o.mnew);
919                                 }
920                         }
921                 w = w->link.next;
922                 } while (w != maint->topwin);
923         }
924         return 0;
925 }
926
927 int ulose(BW *bw)
928 {
929         msgnw(bw->parent, NULL);
930         if (bw->b->count==1 && bw->b->pid) {
931                 return ukillpid(bw);
932         }
933         if (bw->b->changed && !bw->b->scratch) {
934                 if (mkqw(bw->parent, sc("Lose changes to this file (y,n,^C)? "), dolose, NULL, NULL, NULL)) {
935                         return 0;
936                 } else {
937                         return -1;
938                 }
939         } else {
940                 return dolose(bw, 'y', NULL, NULL);
941         }
942 }
943
944 /* Buffer list */
945
946 static int dobuf(MENU *m, int x, unsigned char **s)
947 {
948         unsigned char *name;
949         BW *bw = m->parent->win->object;
950         int *notify = m->parent->notify;
951
952         m->parent->notify = 0;
953         name = vsdup(s[x]);
954         wabort(m->parent);
955         return dorepl(bw, name, NULL, notify);
956 }
957
958 static int abrtb(MENU *m, int x, unsigned char **s)
959 {
960         varm(s);
961         return -1;
962 }
963
964 int ubufed(BW *bw)
965 {
966         unsigned char **s = getbufs();
967
968         vasort(av(s));
969         if (mkmenu(bw->parent, s, dobuf, abrtb, NULL, 0, s, NULL))
970                 return 0;
971         else {
972                 varm(s);
973                 return -1;
974         }
975 }
976
977 /* Query save loop */
978
979 static int doquerysave(BW *bw,int c,struct savereq *req,int *notify)
980 {
981         W *w = bw->parent;
982         if (c == 'y' || c == 'Y') {
983                 if (bw->b->name && bw->b->name[0])
984                         return dosave1(bw, vsncpy(NULL,0,sz(bw->b->name)), req, notify);
985                 else {
986                         BW *pbw;
987                         pbw = wmkpw(bw->parent, US "Name of file to save (^C to abort): ", &filehist, dosave1, US "Names", NULL, cmplt, req, notify, locale_map);
988
989                         if (pbw) {
990                                 return 0;
991                         } else {
992                                 joe_free(req);
993                                 return -1;
994                         }
995                 }
996         } else if (c == 'n' || c == 'N') {
997                 /* Find next buffer to save */
998                 if (bw->b->changed)
999                         req->not_saved = 1;
1000                 next:
1001                 if (unbuf(bw)) {
1002                         if (notify)
1003                                 *notify = 1;
1004                         rmsavereq(req);
1005                         return -1;
1006                 }
1007                 bw = w->object;
1008                 if (bw->b==req->first) {
1009                         if (notify)
1010                                 *notify = 1;
1011                         rmsavereq(req);
1012                         genexmsgmulti(bw,1,req->not_saved);
1013                         return 0;
1014                 }
1015                 if (!bw->b->changed || bw->b->scratch)
1016                         goto next;
1017
1018                 return doquerysave(bw,0,req,notify);
1019         } else {
1020                 unsigned char buf[1024];
1021                 joe_snprintf_1(buf,1024,"File %s has been modified.  Save it (y,n,^C)? ",bw->b->name ? bw->b->name : US "(Unnamed)" );
1022                 if (mkqw(bw->parent, sz(buf), doquerysave, NULL, req, notify)) {
1023                         return 0;
1024                         } else {
1025                         /* Should be in abort function */
1026                         rmsavereq(req);
1027                         return -1;
1028                 }
1029         }
1030 }
1031
1032 static int query_next(BW *bw, struct savereq *req,int flg,int *notify)
1033 {
1034         if (flg) {
1035                 if (notify)
1036                         *notify = 1;
1037                 rmsavereq(req);
1038                 return -1;
1039         } else
1040                 return doquerysave(bw,'N',req,notify);
1041 }
1042
1043 int uquerysave(BW *bw)
1044 {
1045         W *w = bw->parent;
1046         B *first = bw->b;
1047
1048         /* Find a modified buffer */
1049         do {
1050                 if (bw->b->changed && !bw->b->scratch)
1051                         return doquerysave(bw,0,mksavereq(query_next,NULL,first,0),NULL);
1052                 else if (unbuf(bw))
1053                         return -1;
1054                 bw = w->object;
1055         } while(bw->b!=first);
1056
1057         genexmsgmulti(bw,0,0);
1058
1059         return 0;
1060 }
1061
1062 int ukilljoe(BW *bw)
1063 {
1064         leave = 1;
1065         return 0;
1066 }
1067
1068 extern int main_rv;
1069
1070 int
1071 uabendjoe(BW *bw)
1072 {
1073         main_rv = 1;
1074         return (ukilljoe(bw));
1075 }