1 #include "arch.h"
2 #include "deco.h"
3 #include "exec.h"
4 #include "extr.h"
5 #include "fs.h"
6 #include "link.h"
7 #include "math.h"
8 #include "mem.h"
9 #include "msg.h"
10 #include "str.h"
11
12 #include <ctype.h>
13 #include <dirent.h>
14 #include <errno.h>
15 #include <stdbool.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <strings.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23
24
setup(struct archive * a)25 static bool setup(struct archive *a)
26 {
27 size_t lexten = 0;
28
29 switch (file_type(true, a->file.p))
30 {
31 case Unknown: return false;
32 case None: return err(MSG_NOT_EXIST, 0, a->file.p);
33 case Dir: return err(MSG_IS_DIR, 0, a->file.p);
34 }
35 a->file = build_path(a->file.p);
36
37 if (Extension)
38 {
39 lexten = strlen(Extension);
40 if (elen(a->file) > lexten + 1
41 && a->file.z[-lexten-1] == '.'
42 && strcasecmp(a->file.z - lexten, Extension) == 0)
43 lexten = 0;
44 }
45 a->links.file.e = alloc(elen(a->file) + !!lexten + lexten + 1);
46 a->links.file.z = str_cp_max(lexten, str_cp_max(lexten, str_cp(a->links.file.e, a->file.e), "."), Extension);
47
48 find_extr(a);
49 if (!a->extr)
50 return err(MSG_FIND_EXTR, 0, a->file.p);
51 str_tr(tolower, a->links.file.e + a->lname);
52 return true;
53 }
54
prefix_num(unsigned int try,char * buf,const char * e,size_t le)55 static char *prefix_num(unsigned int try, char *buf, const char *e, size_t le)
56 {
57 switch (try)
58 {
59 case 1: buf = str_cp(buf, "1-");
60 case 0: return str_cp_max(le, buf, e);
61 }
62 buf[0] = '0' + try;
63 return buf + 2 + le;
64 }
65
create_sandbox(struct archive * a)66 static bool create_sandbox(struct archive *a)
67 {
68 size_t ldir;
69 unsigned int i;
70
71 ldir = Absolute ? dlen(a->file) : 0;
72 a->sandbox.p = alloc(ldir + 2 + a->lname + 1);
73 a->sandbox.e = str_cp_max(ldir, a->sandbox.p, a->file.p);
74
75 for (i = 0; i < Tries; ++i)
76 {
77 a->sandbox.z = prefix_num(i, a->sandbox.e, a->links.file.e, a->lname);
78 if (mkdir(a->sandbox.p, S_IRWXU | S_IRWXG | S_IRWXO) == 0)
79 return true;
80 if (errno != EEXIST)
81 break;
82 }
83 return err(MSG_MKDIR, errno, a->sandbox.p);
84 }
85
exec_extr(void * args)86 static void exec_extr(void *args)
87 {
88 struct archive *a = args;
89 char *cmd[] = { a->extr->extract, a->links.file.e - 2, NULL }, *name;
90
91 if (chdir(a->sandbox.p) != 0)
92 die(MSG_CHDIR, errno, a->sandbox.p);
93 name = alloc(strlen("Name=") + a->lname + 1);
94 str_cp_max(a->lname, str_cp(name, "Name="), a->links.file.e);
95 if (!put_env(name))
96 exit(EXIT_FAILURE);
97 cmd[1][0] = '.';
98 exec(cmd);
99 }
100
norm_perms(const struct archive * a)101 static bool norm_perms(const struct archive *a)
102 {
103 const char *args[] = { "chmod", "-R", "a=,+rwX", a->sandbox.p, NULL };
104
105 return !a->extr->perms || spawn(exec, args, MSG_NORM_PERMS, a->sandbox.p);
106 }
107
inc_rename(unsigned int tries,const char * src,enum type typ,char * dst,size_t offset,const struct path ent)108 static int inc_rename(unsigned int tries, const char *src, enum type typ,
109 char *dst, size_t offset, const struct path ent)
110 {
111 unsigned int i;
112
113 if (typ > None)
114 for (i = 0; i < tries; ++i)
115 {
116 prefix_num(i, dst + offset, ent.e, elen(ent));
117 switch (safe_rename(src, typ, dst))
118 {
119 case -1: return 0;
120 case 1: return i + 1;
121 }
122 }
123 return 0;
124 }
125
move_lower(const struct path dir,const struct path only,enum type * typ)126 static char *move_lower(const struct path dir, const struct path only, enum type *typ)
127 {
128 /*
129 * rename parent/last/only to parent/only (or parent/n-only)
130 * rmdir parent/last == dir
131 * try to remove n- prefix (or lower n)
132 */
133
134 unsigned int try;
135 char *p, *q;
136
137 p = alloc(plen(dir) + 1 + elen(only) + 1); /* plen(dir) + 1 >= ... */
138 str_cp(str_cp(str_cp(p, dir.p), "/"), only.e);
139 q = alloc(dlen(dir) + 2 + elen(only) + 1); /* ... dlen(dir) + 2 */
140 str_cp_max(dlen(dir), q, dir.p);
141
142 *typ = file_type(false, p);
143 try = inc_rename(Tries + 1, p, *typ, q, dlen(dir), only);
144 if (!try)
145 {
146 err(MSG_RENAME, errno, p);
147 free(p);
148 free(q);
149 return NULL;
150 }
151 if (rmdir(dir.p) != 0)
152 err(MSG_DEL, errno, dir.p);
153 try = inc_rename(MIN(try - 1, Tries), q, *typ, p, dlen(dir), only);
154 free(try ? q : p);
155 return try ? p : q;
156 }
157
handle_contents(const struct archive * a)158 static bool handle_contents(const struct archive *a)
159 {
160 struct path first;
161 enum type typ;
162 DIR *dir;
163 struct dirent *ent;
164 char *moved;
165 unsigned int entries = 0;
166
167 dir = open_dir(a->sandbox.p);
168 if (!dir)
169 return false;
170 while ((ent = readdir(dir)))
171 {
172 if (is_dot(ent->d_name))
173 continue;
174 if (++entries > 1 || a->extr->subdir)
175 break;
176 first.p = first.e = alloc(strlen(ent->d_name) + 1);
177 first.z = str_cp(first.e, ent->d_name);
178 }
179 closedir(dir);
180
181 if (entries == 0)
182 {
183 err(MSG_EMPTY, 0, a->file.p);
184 return rmdir(a->sandbox.p) == 0 || err(MSG_DEL, errno, a->sandbox.p);
185 }
186 if (entries == 1 && !a->extr->subdir)
187 {
188 moved = move_lower(a->sandbox, first, &typ);
189 free(first.p);
190 if (moved)
191 {
192 pretty_print(moved, typ);
193 free(moved);
194 return true;
195 }
196 return false;
197 }
198 /* entries > 1 || a->extr->subdir */
199 if (!a->extr->subdir)
200 free(first.p);
201 pretty_print(a->sandbox.p, Dir);
202 return true;
203 }
204
del_arch(const struct archive * a)205 static bool del_arch(const struct archive *a)
206 {
207 struct path file;
208 size_t longest = 0;
209 const struct link *l;
210 bool success = true;
211
212 if (!Unlink)
213 return true;
214
215 for (l = &a->links; l; l = l->next)
216 longest = MAX(longest, elen(l->target));
217 file.p = alloc(dlen(a->file) + longest + 1);
218 file.e = str_cp_max(dlen(a->file), file.p, a->file.p);
219 for (l = &a->links; l; l = l->next)
220 {
221 str_cp(file.e, l->target.e);
222 if (unlink(file.p) != 0)
223 success = err(MSG_DEL, errno, file.p);
224 }
225 free(file.p);
226 return success;
227 }
228
extract(const char * arg)229 bool extract(const char *arg)
230 {
231 struct archive a = { (char *)arg };
232 bool success = false, ex;
233
234 if (setup(&a))
235 {
236 if (create_sandbox(&a))
237 if (create_links(&a)
238 && ((ex = spawn(exec_extr, &a, MSG_EXTRACT, a.file.p)) || !Clean)
239 && del_links(&a)
240 && norm_perms(&a)
241 && handle_contents(&a))
242 success = ex && del_arch(&a);
243 else
244 rm_rf(a.sandbox.p);
245 free(a.sandbox.p); /* allocated by create_sandbox() */
246 }
247 free_links(&a); /* allocated by setup() and create_links() */
248 return success;
249 }
250