1/*
2 * Create a squashfs filesystem.  This is a highly compressed read only
3 * filesystem.
4 *
5 * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
6 * 2013, 2014
7 * Phillip Lougher <phillip@squashfs.org.uk>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2,
12 * or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 *
23 * sort.c
24 */
25
26#define TRUE 1
27#define FALSE 0
28#define MAX_LINE 16384
29
30#include <unistd.h>
31#include <stdio.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <errno.h>
36#include <dirent.h>
37#include <string.h>
38#include <stdlib.h>
39#include <ctype.h>
40
41#include "squashfs_fs.h"
42#include "mksquashfs.h"
43#include "sort.h"
44#include "error.h"
45#include "progressbar.h"
46
47int mkisofs_style = -1;
48
49struct sort_info {
50	dev_t			st_dev;
51	ino_t			st_ino;
52	int			priority;
53	struct sort_info	*next;
54};
55
56struct sort_info *sort_info_list[65536];
57
58struct priority_entry *priority_list[65536];
59
60extern int silent;
61extern void write_file(squashfs_inode *inode, struct dir_ent *dir_ent,
62	int *c_size);
63extern char *pathname(struct dir_ent *dir_ent);
64
65
66void add_priority_list(struct dir_ent *dir, int priority)
67{
68	struct priority_entry *new_priority_entry;
69
70	priority += 32768;
71	new_priority_entry = malloc(sizeof(struct priority_entry));
72	if(new_priority_entry == NULL)
73		MEM_ERROR();
74
75	new_priority_entry->dir = dir;;
76	new_priority_entry->next = priority_list[priority];
77	priority_list[priority] = new_priority_entry;
78}
79
80
81int get_priority(char *filename, struct stat *buf, int priority)
82{
83	int hash = buf->st_ino & 0xffff;
84	struct sort_info *s;
85
86	for(s = sort_info_list[hash]; s; s = s->next)
87		if((s->st_dev == buf->st_dev) && (s->st_ino == buf->st_ino)) {
88			TRACE("returning priority %d (%s)\n", s->priority,
89				filename);
90			return s->priority;
91		}
92	TRACE("returning priority %d (%s)\n", priority, filename);
93	return priority;
94}
95
96
97#define ADD_ENTRY(buf, priority) {\
98	int hash = buf.st_ino & 0xffff;\
99	struct sort_info *s;\
100	if((s = malloc(sizeof(struct sort_info))) == NULL) \
101		MEM_ERROR(); \
102	s->st_dev = buf.st_dev;\
103	s->st_ino = buf.st_ino;\
104	s->priority = priority;\
105	s->next = sort_info_list[hash];\
106	sort_info_list[hash] = s;\
107	}
108int add_sort_list(char *path, int priority, int source, char *source_path[])
109{
110	int i, n;
111	struct stat buf;
112
113	TRACE("add_sort_list: filename %s, priority %d\n", path, priority);
114	if(strlen(path) > 1 && strcmp(path + strlen(path) - 2, "/*") == 0)
115		path[strlen(path) - 2] = '\0';
116
117	TRACE("add_sort_list: filename %s, priority %d\n", path, priority);
118re_read:
119	if(path[0] == '/' || strncmp(path, "./", 2) == 0 ||
120			strncmp(path, "../", 3) == 0 || mkisofs_style == 1) {
121		if(lstat(path, &buf) == -1)
122			goto error;
123		TRACE("adding filename %s, priority %d, st_dev %d, st_ino "
124			"%lld\n", path, priority, (int) buf.st_dev,
125			(long long) buf.st_ino);
126		ADD_ENTRY(buf, priority);
127		return TRUE;
128	}
129
130	for(i = 0, n = 0; i < source; i++) {
131		char *filename;
132		int res = asprintf(&filename, "%s/%s", source_path[i], path);
133		if(res == -1)
134			BAD_ERROR("asprintf failed in add_sort_list\n");
135		res = lstat(filename, &buf);
136		free(filename);
137		if(res == -1) {
138			if(!(errno == ENOENT || errno == ENOTDIR))
139				goto error;
140			continue;
141		}
142		ADD_ENTRY(buf, priority);
143		n ++;
144	}
145
146	if(n == 0 && mkisofs_style == -1 && lstat(path, &buf) != -1) {
147		ERROR("WARNING: Mkisofs style sortlist detected! This is "
148			"supported but please\n");
149		ERROR("convert to mksquashfs style sortlist! A sortlist entry");
150	        ERROR(" should be\neither absolute (starting with ");
151		ERROR("'/') start with './' or '../' (taken to be\nrelative to "
152			"$PWD), otherwise it ");
153		ERROR("is assumed the entry is relative to one\nof the source "
154			"directories, i.e. with ");
155		ERROR("\"mksquashfs test test.sqsh\",\nthe sortlist ");
156		ERROR("entry \"file\" is assumed to be inside the directory "
157			"test.\n\n");
158		mkisofs_style = 1;
159		goto re_read;
160	}
161
162	mkisofs_style = 0;
163
164	if(n == 1)
165		return TRUE;
166	if(n > 1) {
167		ERROR(" Ambiguous sortlist entry \"%s\"\n\nIt maps to more "
168			"than one source entry!  Please use an absolute path."
169			"\n", path);
170		return FALSE;
171	}
172
173error:
174        ERROR_START("Cannot stat sortlist entry \"%s\"\n", path);
175        ERROR("This is probably because you're using the wrong file\n");
176        ERROR("path relative to the source directories.");
177	ERROR_EXIT("  Ignoring");
178	/*
179	 * Historical note
180	 * Failure to stat a sortlist entry is deliberately ignored, even
181	 * though it is an error.  Squashfs release 2.2 changed the behaviour
182	 * to treat it as a fatal error, but it was changed back to
183	 * the original behaviour to ignore it in release 2.2-r2 following
184	 * feedback from users at the time.
185	 */
186        return TRUE;
187}
188
189
190void generate_file_priorities(struct dir_info *dir, int priority,
191	struct stat *buf)
192{
193	struct dir_ent *dir_ent = dir->list;
194
195	priority = get_priority(dir->pathname, buf, priority);
196
197	for(; dir_ent; dir_ent = dir_ent->next) {
198		struct stat *buf = &dir_ent->inode->buf;
199		if(dir_ent->inode->root_entry)
200			continue;
201
202		switch(buf->st_mode & S_IFMT) {
203			case S_IFREG:
204				add_priority_list(dir_ent,
205					get_priority(pathname(dir_ent), buf,
206					priority));
207				break;
208			case S_IFDIR:
209				generate_file_priorities(dir_ent->dir,
210					priority, buf);
211				break;
212		}
213	}
214}
215
216
217int read_sort_file(char *filename, int source, char *source_path[])
218{
219	FILE *fd;
220	char line_buffer[MAX_LINE + 1]; /* overflow safe */
221	char sort_filename[MAX_LINE + 1]; /* overflow safe */
222	char *line, *name;
223	int n, priority, res;
224
225	if((fd = fopen(filename, "r")) == NULL) {
226		ERROR("Failed to open sort file \"%s\" because %s\n",
227			filename, strerror(errno));
228		return FALSE;
229	}
230
231	while(fgets(line = line_buffer, MAX_LINE + 1, fd) != NULL) {
232		int len = strlen(line);
233
234		if(len == MAX_LINE && line[len - 1] != '\n') {
235			/* line too large */
236			ERROR("Line too long when reading "
237				"sort file \"%s\", larger than %d "
238				"bytes\n", filename, MAX_LINE);
239			goto failed;
240		}
241
242		/*
243		 * Remove '\n' terminator if it exists (the last line
244		 * in the file may not be '\n' terminated)
245		 */
246		if(len && line[len - 1] == '\n')
247			line[len - 1] = '\0';
248
249		/* Skip any leading whitespace */
250		while(isspace(*line))
251			line ++;
252
253		/* if comment line, skip */
254		if(*line == '#')
255			continue;
256
257		/*
258		 * Scan for filename, don't use sscanf() and "%s" because
259		 * that can't handle filenames with spaces
260		 */
261		for(name = sort_filename; !isspace(*line) && *line != '\0';) {
262			if(*line == '\\') {
263				line ++;
264				if (*line == '\0')
265					break;
266			}
267			*name ++ = *line ++;
268		}
269		*name = '\0';
270
271		/*
272		 * if filename empty, then line was empty of anything but
273		 * whitespace or a backslash character.  Skip empy lines
274		 */
275		if(sort_filename[0] == '\0')
276			continue;
277
278		/*
279		 * Scan the rest of the line, we expect a decimal number
280		 * which is the filename priority
281		 */
282		errno = 0;
283		res = sscanf(line, "%d%n", &priority, &n);
284
285		if((res < 1 || errno) && errno != ERANGE) {
286			if(errno == 0)
287				/* No error, assume EOL or match failure */
288				ERROR("Sort file \"%s\", can't find priority "
289					"in entry \"%s\", EOL or match "
290					"failure\n", filename, line_buffer);
291			else
292				/* Some other failure not ERANGE */
293				ERROR("Sscanf failed reading sort file \"%s\" "
294					"because %s\n", filename,
295					strerror(errno));
296			goto failed;
297		} else if((errno == ERANGE) ||
298				(priority < -32768 || priority > 32767)) {
299			ERROR("Sort file \"%s\", entry \"%s\" has priority "
300				"outside range of -32767:32768.\n", filename,
301				line_buffer);
302			goto failed;
303		}
304
305		/* Skip any trailing whitespace */
306		line += n;
307		while(isspace(*line))
308			line ++;
309
310		if(*line != '\0') {
311			ERROR("Sort file \"%s\", trailing characters after "
312				"priority in entry \"%s\"\n", filename,
313				line_buffer);
314			goto failed;
315		}
316
317		res = add_sort_list(sort_filename, priority, source,
318			source_path);
319		if(res == FALSE)
320			goto failed;
321	}
322
323	if(ferror(fd)) {
324		ERROR("Reading sort file \"%s\" failed because %s\n", filename,
325			strerror(errno));
326		goto failed;
327	}
328
329	fclose(fd);
330	return TRUE;
331
332failed:
333	fclose(fd);
334	return FALSE;
335}
336
337
338void sort_files_and_write(struct dir_info *dir)
339{
340	int i;
341	struct priority_entry *entry;
342	squashfs_inode inode;
343	int duplicate_file;
344
345	for(i = 65535; i >= 0; i--)
346		for(entry = priority_list[i]; entry; entry = entry->next) {
347			TRACE("%d: %s\n", i - 32768, pathname(entry->dir));
348			if(entry->dir->inode->inode == SQUASHFS_INVALID_BLK) {
349				write_file(&inode, entry->dir, &duplicate_file);
350				INFO("file %s, uncompressed size %lld bytes %s"
351					"\n", pathname(entry->dir),
352					(long long)
353					entry->dir->inode->buf.st_size,
354					duplicate_file ? "DUPLICATE" : "");
355				entry->dir->inode->inode = inode;
356				entry->dir->inode->type = SQUASHFS_FILE_TYPE;
357			} else
358				INFO("file %s, uncompressed size %lld bytes "
359					"LINK\n", pathname(entry->dir),
360					(long long)
361					entry->dir->inode->buf.st_size);
362		}
363}
364