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