1 /* Disk file handling
2 
3    Copyright (c) 1997-2014 Free Software Foundation, Inc.
4 
5    This file is part of GNU Zile.
6 
7    GNU Zile is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3, or (at your option)
10    any later version.
11 
12    GNU Zile is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with GNU Zile; see the file COPYING.  If not, write to the
19    Free Software Foundation, Fifth Floor, 51 Franklin Street, Boston,
20    MA 02111-1301, USA.  */
21 
22 #include <config.h>
23 
24 #include <sys/stat.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <pwd.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <utime.h>
33 #include "dirname.h"
34 #include "xgetcwd.h"
35 #include "copy-file.h"
36 
37 #include "main.h"
38 #include "extern.h"
39 
40 bool
exist_file(const char * filename)41 exist_file (const char *filename)
42 {
43   struct stat st;
44   return !(stat (filename, &st) == -1 && errno == ENOENT);
45 }
46 
47 static int
is_regular_file(const char * filename)48 is_regular_file (const char *filename)
49 {
50   struct stat st;
51   return stat (filename, &st) == 0 && S_ISREG (st.st_mode);
52 }
53 
54 /*
55  * Return the current directory, if available.
56  */
57 astr
agetcwd(void)58 agetcwd (void)
59 {
60   char *s = xgetcwd ();
61   return astr_new_cstr (s ? s : "");
62 }
63 
64 /*
65  * This functions does some corrections and expansions to
66  * the passed path:
67  *
68  * - expands `~/' and `~name/' expressions;
69  * - replaces `//' with `/' (restarting from the root directory);
70  * - removes `..' and `.' entries.
71  *
72  * The return value indicates success or failure.
73  */
74 bool
expand_path(astr path)75 expand_path (astr path)
76 {
77   int ok = true;
78   const char *sp = astr_cstr (path);
79   astr epath = astr_new ();
80 
81   if (*sp != '/' && *sp != '~')
82     {
83       astr_cat (epath, agetcwd ());
84       if (astr_len (epath) == 0 ||
85           astr_get (epath, astr_len (epath) - 1) != '/')
86         astr_cat_char (epath, '/');
87     }
88 
89   for (const char *p = sp; *p != '\0';)
90     {
91       if (*p == '/')
92         {
93           if (*++p == '/')
94             { /* Got `//'.  Restart from this point. */
95               while (*p == '/')
96                 p++;
97               astr_truncate (epath, 0);
98             }
99           if (astr_len (epath) == 0 ||
100               astr_get (epath, astr_len (epath) - 1) != '/')
101             astr_cat_char (epath, '/');
102         }
103       else if (*p == '~' && (p == sp || p[-1] == '/'))
104         { /* Got `/~' or leading `~'.  Restart from this point. */
105           struct passwd *pw;
106 
107           astr_truncate (epath, 0);
108           ++p;
109 
110           if (*p == '/')
111             { /* Got `~/'.  Insert the user's home directory. */
112               pw = getpwuid (getuid ());
113               if (pw == NULL)
114                 {
115                   ok = false;
116                   break;
117                 }
118               if (!STREQ (pw->pw_dir, "/"))
119                 astr_cat_cstr (epath, pw->pw_dir);
120             }
121           else
122             { /* Got `~something'.  Insert that user's home directory. */
123               astr as = astr_new ();
124               while (*p != '\0' && *p != '/')
125                 astr_cat_char (as, *p++);
126               pw = getpwnam (astr_cstr (as));
127               if (pw == NULL)
128                 {
129                   ok = false;
130                   break;
131                 }
132               astr_cat_cstr (epath, pw->pw_dir);
133             }
134         }
135       else if (*p == '.' && (p[1] == '/' || p[1] == '\0'))
136         { /* Got `.'. */
137           ++p;
138         }
139       else if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0'))
140         { /* Got `..'. */
141           if (astr_len (epath) >= 1 && astr_get (epath, astr_len (epath) - 1) == '/')
142             astr_truncate (epath, astr_len (epath) - 1);
143           while (astr_get (epath, astr_len (epath) - 1) != '/' && astr_len (epath) >= 1)
144             astr_truncate (epath, astr_len (epath) - 1);
145           p += 2;
146         }
147 
148       if (*p != '~')
149         while (*p != '\0' && *p != '/')
150           astr_cat_char (epath, *p++);
151     }
152 
153   astr_cpy (path, epath);
154 
155   return ok;
156 }
157 
158 /*
159  * Return a `~/foo' like path if the user is under his home directory,
160  * else the unmodified path.
161  */
162 astr
compact_path(astr path)163 compact_path (astr path)
164 {
165   struct passwd *pw = getpwuid (getuid ());
166 
167   if (pw != NULL)
168     {
169       /* Replace `/userhome/' (if found) with `~/'. */
170       size_t homelen = strlen (pw->pw_dir);
171       if (homelen > 0 && pw->pw_dir[homelen - 1] == '/')
172         homelen--;
173 
174       if (astr_len (path) > homelen &&
175           !strncmp (pw->pw_dir, astr_cstr (path), homelen) &&
176           astr_get (path, homelen) == '/')
177         astr_cpy (path, astr_cat_cstr (astr_new_cstr ("~/"),
178                                        astr_cstr (path) + homelen + 1));
179     }
180 
181   return path;
182 }
183 
184 /*
185  * Get HOME directory.
186  */
187 astr
get_home_dir(void)188 get_home_dir (void)
189 {
190   char *s = getenv ("HOME");
191   if (s != NULL)
192     return astr_new_cstr (s);
193   return NULL;
194 }
195 
196 /* Return true if file exists and can be written. */
197 static bool
check_writable(const char * filename)198 check_writable (const char *filename)
199 {
200   return euidaccess (filename, W_OK) >= 0;
201 }
202 
203 bool
find_file(const char * filename)204 find_file (const char *filename)
205 {
206   Buffer bp;
207   for (bp = head_bp; bp != NULL; bp = get_buffer_next (bp))
208     if (get_buffer_filename (bp) != NULL &&
209         STREQ (get_buffer_filename (bp), filename))
210       break;
211 
212   if (bp == NULL)
213     {
214       if (exist_file (filename) && !is_regular_file (filename))
215         {
216           minibuf_error ("File exists but could not be read");
217           return false;
218         }
219       else
220         {
221           bp = buffer_new ();
222           set_buffer_names (bp, filename);
223           set_buffer_dir (bp, astr_new_cstr (dir_name (filename)));
224 
225           estr es = estr_readf (filename);
226           if (es)
227             set_buffer_readonly (bp, !check_writable (filename));
228           else
229             es = estr_new_astr (astr_new ());
230           set_buffer_text (bp, es);
231 
232           /* Reset undo history. */
233           set_buffer_next_undop (bp, NULL);
234           set_buffer_last_undop (bp, NULL);
235           set_buffer_modified (bp, false);
236         }
237     }
238 
239   switch_to_buffer (bp);
240   thisflag |= FLAG_NEED_RESYNC;
241   return true;
242 }
243 
244 DEFUN_ARGS ("find-file", find_file,
245             STR_ARG (filename))
246 /*+
247 Edit file @i{filename}.
248 Switch to a buffer visiting file @i{filename},
249 creating one if none already exists.
250 +*/
251 {
252   STR_INIT (filename)
253   else
254     {
255       filename = minibuf_read_filename ("Find file: ",
256                                         astr_cstr (get_buffer_dir (cur_bp)), NULL);
257 
258       if (filename == NULL)
259         ok = FUNCALL (keyboard_quit);
260     }
261 
262   if (filename == NULL || astr_len (filename) == 0)
263     ok = leNIL;
264 
265   if (ok != leNIL)
266     ok = bool_to_lisp (find_file (astr_cstr (filename)));
267 }
268 END_DEFUN
269 
270 DEFUN ("find-file-read-only", find_file_read_only)
271 /*+
272 Edit file @i{filename} but don't allow changes.
273 Like `find-file' but marks buffer as read-only.
274 Use @kbd{M-x toggle-read-only} to permit editing.
275 +*/
276 {
277   ok = F_find_file (uniarg, is_uniarg, arglist);
278   if (ok == leT)
279     set_buffer_readonly (cur_bp, true);
280 }
281 END_DEFUN
282 
283 DEFUN ("find-alternate-file", find_alternate_file)
284 /*+
285 Find the file specified by the user, select its buffer, kill previous buffer.
286 If the current buffer now contains an empty file that you just visited
287 (presumably by mistake), use this command to visit the file you really want.
288 +*/
289 {
290   const char *buf = get_buffer_filename (cur_bp);
291   char *base = NULL;
292 
293   if (buf == NULL)
294     buf = astr_cstr (get_buffer_dir (cur_bp));
295   else
296     base = base_name (buf);
297   const_astr ms = minibuf_read_filename ("Find alternate: ", buf, base);
298 
299   ok = leNIL;
300   if (ms == NULL)
301     ok = FUNCALL (keyboard_quit);
302   else if (astr_len (ms) > 0 && check_modified_buffer (cur_bp))
303     {
304       kill_buffer (cur_bp);
305       ok = bool_to_lisp (find_file (astr_cstr (ms)));
306     }
307 }
308 END_DEFUN
309 
310 DEFUN_ARGS ("switch-to-buffer", switch_to_buffer,
311             STR_ARG (buf))
312 /*+
313 Select buffer @i{buffer} in the current window.
314 +*/
315 {
316   Buffer bp = ((get_buffer_next (cur_bp) != NULL) ? get_buffer_next (cur_bp) : head_bp);
317 
318   STR_INIT (buf)
319   else
320     {
321       Completion cp = make_buffer_completion ();
322       buf = minibuf_read_completion ("Switch to buffer (default %s): ",
323                                      "", cp, NULL, get_buffer_name (bp));
324 
325       if (buf == NULL)
326         ok = FUNCALL (keyboard_quit);
327     }
328 
329   if (ok == leT)
330     {
331       if (buf && astr_len (buf) > 0)
332         {
333           bp = find_buffer (astr_cstr (buf));
334           if (bp == NULL)
335             {
336               bp = buffer_new ();
337               set_buffer_name (bp, astr_cstr (buf));
338               set_buffer_needname (bp, true);
339               set_buffer_nosave (bp, true);
340             }
341         }
342 
343       switch_to_buffer (bp);
344     }
345 }
346 END_DEFUN
347 
348 DEFUN_ARGS ("insert-buffer", insert_buffer,
349             STR_ARG (buf))
350 /*+
351 Insert after point the contents of BUFFER.
352 Puts mark after the inserted text.
353 +*/
354 {
355   Buffer def_bp = ((get_buffer_next (cur_bp) != NULL) ? get_buffer_next (cur_bp) : head_bp);
356 
357   if (warn_if_readonly_buffer ())
358     return leNIL;
359 
360   STR_INIT (buf)
361   else
362     {
363       Completion cp = make_buffer_completion ();
364       buf = minibuf_read_completion ("Insert buffer (default %s): ",
365                                      "", cp, NULL, get_buffer_name (def_bp));
366       if (buf == NULL)
367         ok = FUNCALL (keyboard_quit);
368     }
369 
370   if (ok == leT)
371     {
372       Buffer bp;
373 
374       if (buf && astr_len (buf) > 0)
375         {
376           bp = find_buffer (astr_cstr (buf));
377           if (bp == NULL)
378             {
379               minibuf_error ("Buffer `%s' not found", astr_cstr (buf));
380               ok = leNIL;
381             }
382         }
383       else
384         bp = def_bp;
385 
386       if (ok != leNIL)
387         {
388           insert_buffer (bp);
389           FUNCALL (set_mark_command);
390         }
391     }
392 }
393 END_DEFUN
394 
395 DEFUN_ARGS ("insert-file", insert_file,
396             STR_ARG (file))
397 /*+
398 Insert contents of file FILENAME into buffer after point.
399 Set mark after the inserted text.
400 +*/
401 {
402   if (warn_if_readonly_buffer ())
403     return leNIL;
404 
405   STR_INIT (file)
406   else
407     {
408       file = minibuf_read_filename ("Insert file: ",
409                                     astr_cstr (get_buffer_dir (cur_bp)), NULL);
410       if (file == NULL)
411         ok = FUNCALL (keyboard_quit);
412     }
413 
414   if (file == NULL || astr_len (file) == 0)
415     ok = leNIL;
416 
417   if (ok != leNIL)
418     {
419       estr es = estr_readf (astr_cstr (file));
420       if (es)
421         {
422           insert_estr (es);
423           FUNCALL (set_mark_command);
424         }
425       else
426         {
427           ok = leNIL;
428           minibuf_error ("%s: %s", astr_cstr (file), strerror (errno));
429         }
430     }
431 }
432 END_DEFUN
433 
434 /*
435  * Write buffer to given file name with given mode.
436  */
437 static int
write_to_disk(Buffer bp,const char * filename,mode_t mode)438 write_to_disk (Buffer bp, const char *filename, mode_t mode)
439 {
440   int fd = creat (filename, mode);
441   if (fd < 0)
442     return -1;
443 
444   int ret = 0;
445   const_astr as = get_buffer_pre_point (bp);
446   ssize_t written = write (fd, astr_cstr (as), astr_len (as));
447   if (written < 0 || (size_t) written != astr_len (as))
448     ret = written;
449   else
450     {
451       as = get_buffer_post_point (bp);
452       written = write (fd, astr_cstr (as), astr_len (as));
453       if (written < 0 || (size_t) written != astr_len (as))
454         ret = written;
455     }
456 
457   if (close (fd) < 0 && ret == 0)
458     ret = -1;
459 
460   return ret;
461 }
462 
463 /*
464  * Create a backup filename according to user specified variables.
465  */
466 static astr
create_backup_filename(const char * filename,const char * backupdir)467 create_backup_filename (const char *filename, const char *backupdir)
468 {
469   astr res;
470 
471   /* Prepend the backup directory path to the filename */
472   if (backupdir)
473     {
474       astr buf = astr_new_cstr (backupdir);
475       if (astr_get (buf, astr_len (buf) - 1) != '/')
476         astr_cat_char (buf, '/');
477       for (; *filename; filename++)
478         if (*filename == '/')
479           astr_cat_char (buf, '!');
480         else
481           astr_cat_char (buf, *filename);
482 
483       if (!expand_path (buf))
484         buf = NULL;
485       res = buf;
486     }
487   else
488     res = astr_new_cstr (filename);
489 
490   return astr_cat_char (res, '~');
491 }
492 
493 /*
494  * Write the buffer contents to a file.
495  * Create a backup file if specified by the user variables.
496  */
497 static int
backup_and_write(Buffer bp,const char * filename)498 backup_and_write (Buffer bp, const char *filename)
499 {
500   /* Make backup of original file. */
501   int fd, backup = get_variable_bool ("make-backup-files");
502   if (!get_buffer_backup (bp) && backup
503       && (fd = open (filename, O_RDWR, 0)) != -1)
504     {
505       close (fd);
506 
507       const char *backupdir = get_variable_bool ("backup-directory") ?
508         get_variable ("backup-directory") : NULL;
509       astr bfilename = create_backup_filename (filename, backupdir);
510       if (bfilename && qcopy_file_preserving (filename, astr_cstr (bfilename)) == 0)
511         set_buffer_backup (bp, true);
512       else
513         {
514           minibuf_error ("Cannot make backup file: %s", strerror (errno));
515           waitkey ();
516         }
517     }
518 
519   int ret = write_to_disk (bp, filename, S_IRUSR | S_IWUSR |
520                            S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
521   if (ret == 0)
522     return true;
523 
524   if (ret == -1)
525     minibuf_error ("Error writing `%s': %s", filename, strerror (errno));
526   else
527     minibuf_error ("Error writing `%s'", filename);
528   return false;
529 }
530 
531 static le *
write_buffer(Buffer bp,bool needname,bool confirm,const char * name0,const char * prompt)532 write_buffer (Buffer bp, bool needname, bool confirm,
533               const char *name0, const char *prompt)
534 {
535   bool ans = true;
536   le * ok = leT;
537   const_astr name;
538 
539   if (!needname)
540     name = astr_new_cstr (name0);
541 
542   if (needname)
543     {
544       name = minibuf_read_filename ("%s", "", NULL, prompt);
545       if (name == NULL)
546         return FUNCALL (keyboard_quit);
547       if (astr_len (name) == 0)
548         return leNIL;
549       confirm = true;
550     }
551 
552   if (confirm && exist_file (astr_cstr (name)))
553     {
554       char *buf = xasprintf ("File `%s' exists; overwrite? (y or n) ", astr_cstr (name));
555       const char *errmsg = "";
556 
557       for (ans = -2; ans == -2;) {
558         minibuf_write ("%s%s", errmsg, buf);
559         switch (getkeystroke (GETKEY_DEFAULT))
560           {
561           case 'y':
562           case 'Y':
563           case ' ':
564           case KBD_RET:
565             ans = true;
566             break;
567           case 'N':
568           case 'n':
569           case KBD_DEL:
570             ans = false;
571             break;
572           case KBD_CTRL | 'g':
573             ans = -1;
574           default:
575             errmsg = "Please answer y or n.  ";
576           }
577       }
578 
579       if (ans == -1)
580         FUNCALL (keyboard_quit);
581       else if (ans == false)
582         minibuf_error ("Canceled");
583       if (ans != true)
584         ok = leNIL;
585     }
586 
587   if (ans == true)
588     {
589       if (get_buffer_filename (bp) == NULL ||
590           !STREQ (astr_cstr (name), get_buffer_filename (bp)))
591         set_buffer_names (bp, astr_cstr (name));
592       set_buffer_needname (bp, false);
593       set_buffer_temporary (bp, false);
594       set_buffer_nosave (bp, false);
595       if (backup_and_write (bp, astr_cstr (name)))
596         {
597           minibuf_write ("Wrote %s", astr_cstr (name));
598           set_buffer_modified (bp, false);
599           undo_set_unchanged (get_buffer_last_undop (bp));
600         }
601       else
602         ok = leNIL;
603     }
604 
605   return ok;
606 }
607 
608 static le *
save_buffer(Buffer bp)609 save_buffer (Buffer bp)
610 {
611   if (get_buffer_modified (bp))
612     return write_buffer (bp, get_buffer_needname (bp), false, get_buffer_filename (bp),
613                          "File to save in: ");
614 
615   minibuf_write ("(No changes need to be saved)");
616   return leT;
617 }
618 
619 DEFUN ("save-buffer", save_buffer)
620 /*+
621 Save current buffer in visited file if modified.  By default, makes the
622 previous version into a backup file if this is the first save.
623 +*/
624 {
625   ok = save_buffer (cur_bp);
626 }
627 END_DEFUN
628 
629 DEFUN ("write-file", write_file)
630 /*+
631 Write current buffer into file @i{filename}.
632 This makes the buffer visit that file, and marks it as not modified.
633 
634 Interactively, confirmation is required unless you supply a prefix argument.
635 +*/
636 {
637   ok = write_buffer (cur_bp, true,
638                      arglist && !(lastflag & FLAG_SET_UNIARG),
639                      NULL, "Write file: ");
640 }
641 END_DEFUN
642 
643 static int
save_some_buffers(void)644 save_some_buffers (void)
645 {
646   bool none_to_save = true;
647   bool noask = false;
648 
649   for (Buffer bp = head_bp; bp != NULL; bp = get_buffer_next (bp))
650     {
651       if (get_buffer_modified (bp) && !get_buffer_nosave (bp))
652         {
653           const char *fname = get_buffer_filename_or_name (bp);
654 
655           none_to_save = false;
656 
657           if (noask)
658             save_buffer (bp);
659           else
660             for (;;)
661               {
662                 int c;
663 
664                 minibuf_write ("Save file %s? (y, n, !, ., q) ", fname);
665                 c = getkey (GETKEY_DEFAULT);
666                 minibuf_clear ();
667 
668                 switch (c)
669                   {
670                   case KBD_CANCEL:	/* C-g */
671                     FUNCALL (keyboard_quit);
672                     return false;
673                   case 'q':
674                     goto endoffunc;
675                   case '.':
676                     save_buffer (bp);
677                     return true;
678                   case '!':
679                     noask = true;
680                     /* FALLTHROUGH */
681                   case ' ':
682                   case 'y':
683                     save_buffer (bp);
684                     /* FALLTHROUGH */
685                   case 'n':
686                   case KBD_RET:
687                   case KBD_DEL:
688                     goto exitloop;
689                   default:
690                     minibuf_error ("Please answer y, n, !, . or q.");
691                     waitkey ();
692                     break;
693                   }
694               }
695         }
696     exitloop:
697       (void)0; /* Label not allowed at end of compound statement. */
698     }
699 
700 endoffunc:
701   if (none_to_save)
702     minibuf_write ("(No files need saving)");
703 
704   return true;
705 }
706 
707 
708 DEFUN ("save-some-buffers", save_some_buffers)
709 /*+
710 Save some modified file-visiting buffers.  Asks user about each one.
711 +*/
712 {
713   ok = bool_to_lisp (save_some_buffers ());
714 }
715 END_DEFUN
716 
717 DEFUN ("save-buffers-kill-emacs", save_buffers_kill_emacs)
718 /*+
719 Offer to save each buffer, then kill this Zile process.
720 +*/
721 {
722   if (!save_some_buffers ())
723     return leNIL;
724 
725   for (Buffer bp = head_bp; bp != NULL; bp = get_buffer_next (bp))
726     if (get_buffer_modified (bp) && !get_buffer_needname (bp))
727       {
728         for (;;)
729           {
730             int ans = minibuf_read_yesno
731               ("Modified buffers exist; exit anyway? (yes or no) ");
732             if (ans == -1)
733               return FUNCALL (keyboard_quit);
734             else if (!ans)
735               return leNIL;
736             break;
737           }
738         break; /* We have found a modified buffer, so stop. */
739       }
740 
741   thisflag |= FLAG_QUIT;
742 }
743 END_DEFUN
744 
745 /*
746  * Function called on unexpected error or Zile crash (SIGSEGV).
747  * Attempts to save modified buffers.
748  * If doabort is true, aborts to allow core dump generation;
749  * otherwise, exit.
750  */
751 void
zile_exit(bool doabort)752 zile_exit (bool doabort)
753 {
754   fprintf (stderr, "Trying to save modified buffers (if any)...\r\n");
755   for (Buffer bp = head_bp; bp != NULL; bp = get_buffer_next (bp))
756     if (get_buffer_modified (bp) && !get_buffer_nosave (bp))
757       {
758         astr buf = astr_fmt ("%s.%sSAVE",
759                              get_buffer_filename_or_name (bp),
760                              astr_cstr (astr_recase (astr_new_cstr (PACKAGE), case_upper)));
761         fprintf (stderr, "Saving %s...\r\n", astr_cstr (buf));
762         write_to_disk (bp, astr_cstr (buf), S_IRUSR | S_IWUSR);
763       }
764 
765   if (doabort)
766     abort ();
767   else
768     exit (EXIT_CRASH);
769 }
770 
771 DEFUN_ARGS ("cd", cd,
772        STR_ARG (dir))
773 /*+
774 Make DIR become the current buffer's default directory.
775 +*/
776 {
777   if (arglist == NULL)
778     dir = minibuf_read_filename ("Change default directory: ",
779                                  astr_cstr (get_buffer_dir (cur_bp)), NULL);
780 
781   if (dir == NULL)
782     ok = FUNCALL (keyboard_quit);
783   else if (astr_len (dir) > 0)
784     {
785       struct stat st;
786 
787       if (stat (astr_cstr (dir), &st) != 0 || !S_ISDIR (st.st_mode))
788         {
789           minibuf_error ("`%s' is not a directory", astr_cstr (dir));
790           ok = leNIL;
791         }
792       else if (chdir (astr_cstr (dir)) == -1)
793         {
794           minibuf_write ("%s: %s", astr_cstr (dir), strerror (errno));
795           ok = leNIL;
796         }
797     }
798 }
799 END_DEFUN
800