ee87ce1b29ce13d188a6378fe06f8ba3bc61a9ab
[libguestfs.git] / fish / tilde.c
1 /* guestfish - the filesystem interactive shell
2  * Copyright (C) 2009 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <assert.h>
26 #include <pwd.h>
27 #include <sys/types.h>
28
29 #include "fish.h"
30
31 static char *expand_home (char *orig, const char *append);
32 static const char *find_home_for_username (const char *, size_t);
33 static const char *find_home_for_current_user (void);
34
35 /* This is called from the script loop if we find a candidate for
36  * ~username (tilde-expansion).
37  */
38 char *
39 try_tilde_expansion (char *str)
40 {
41   assert (str[0] == '~');
42
43   /* Expand "~" to current user's home directory. */
44   if (str[1] == '\0')           /* ~ */
45     return expand_home (str, NULL);
46   else if (str[1] == '/')       /* ~/... */
47     return expand_home (str, &str[1]);
48
49   /* Try expanding the part up to the following '\0' or '/' as a
50    * username from the password file.
51    */
52   else {
53     const char *home, *rest;
54     size_t len = strcspn (&str[1], "/");
55     rest = &str[1+len];
56
57     home = find_home_for_username (&str[1], len);
58
59     if (home) {
60       len = strlen (home) + strlen (rest) + 1;
61       str = malloc (len);
62       if (str == NULL) {
63         perror ("malloc");
64         exit (EXIT_FAILURE);
65       }
66       strcpy (str, home);
67       strcat (str, rest);
68       return str;
69     }
70   }
71
72   /* No match, return the orignal string. */
73   return str;
74 }
75
76 /* Return $HOME + append string. */
77 static char *
78 expand_home (char *orig, const char *append)
79 {
80   const char *home;
81   int len;
82   char *str;
83
84   home = getenv ("HOME");
85   if (!home) {
86     /* $HOME not set, bash can look up the current user in the
87      * password file and find their home that way.  (RHBZ#617440).
88      */
89     home = find_home_for_current_user ();
90     if (!home)
91       return orig;
92   }
93
94   len = strlen (home) + (append ? strlen (append) : 0) + 1;
95   str = malloc (len);
96   if (str == NULL) {
97     perror ("malloc");
98     exit (EXIT_FAILURE);
99   }
100
101   strcpy (str, home);
102   if (append)
103     strcat (str, append);
104
105   return str;
106 }
107
108 /* Lookup username (of length ulen), return home directory if found,
109  * or NULL if not found.
110  */
111 static const char *
112 find_home_for_username (const char *username, size_t ulen)
113 {
114   struct passwd *pw;
115
116   setpwent ();
117   while ((pw = getpwent ()) != NULL) {
118     if (strlen (pw->pw_name) == ulen &&
119         STREQLEN (username, pw->pw_name, ulen))
120       return pw->pw_dir;
121   }
122
123   return NULL;
124 }
125
126 static const char *
127 find_home_for_current_user (void)
128 {
129   struct passwd *pw;
130   uid_t euid = geteuid ();
131
132   setpwent ();
133   while ((pw = getpwent ()) != NULL) {
134     if (pw->pw_uid == euid)
135       return pw->pw_dir;
136   }
137
138   return NULL;
139 }