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