summaryrefslogtreecommitdiffstats
path: root/libtar/encode.c
blob: c9371527bf4cd19a6b2168926f6111f4cf567d70 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
**  Copyright 1998-2003 University of Illinois Board of Trustees
**  Copyright 1998-2003 Mark D. Roth
**  All rights reserved.
**
**  encode.c - libtar code to encode tar header blocks
**
**  Mark D. Roth <roth@uiuc.edu>
**  Campus Information Technologies and Educational Services
**  University of Illinois at Urbana-Champaign
*/

#include <internal.h>

#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>

#ifdef STDC_HEADERS
# include <string.h>
# include <stdlib.h>
#endif


/* magic, version, and checksum */
void
th_finish(TAR *t)
{
	if (t->options & TAR_GNU)
	{
		/* we're aiming for this result, but must do it in
		 * two calls to avoid FORTIFY segfaults on some Linux
		 * systems:
		 *      strncpy(t->th_buf.magic, "ustar  ", 8);
		 */
		strncpy(t->th_buf.magic, "ustar ", 6);
		strncpy(t->th_buf.version, " ", 2);
	}
	else
	{
		strncpy(t->th_buf.version, TVERSION, TVERSLEN);
		strncpy(t->th_buf.magic, TMAGIC, TMAGLEN);
	}

	int_to_oct(th_crc_calc(t), t->th_buf.chksum, 8);
}


/* map a file mode to a typeflag */
void
th_set_type(TAR *t, mode_t mode)
{
	if (S_ISLNK(mode))
		t->th_buf.typeflag = SYMTYPE;
	if (S_ISREG(mode))
		t->th_buf.typeflag = REGTYPE;
	if (S_ISDIR(mode))
		t->th_buf.typeflag = DIRTYPE;
	if (S_ISCHR(mode))
		t->th_buf.typeflag = CHRTYPE;
	if (S_ISBLK(mode))
		t->th_buf.typeflag = BLKTYPE;
	if (S_ISFIFO(mode) || S_ISSOCK(mode))
		t->th_buf.typeflag = FIFOTYPE;
}


/* encode file path */
void
th_set_path(TAR *t, const char *pathname)
{
	char suffix[2] = "";
	char *tmp;
	size_t pathname_len = strlen(pathname);

#ifdef DEBUG
	printf("in th_set_path(th, pathname=\"%s\")\n", pathname);
#endif

	if (t->th_buf.gnu_longname != NULL)
		free(t->th_buf.gnu_longname);
	t->th_buf.gnu_longname = NULL;

	/* old archive compatibility (not needed for gnu): add trailing / to directories */
	if (pathname[pathname_len - 1] != '/' && TH_ISDIR(t))
		strcpy(suffix, "/");

	if (pathname_len >= T_NAMELEN && (t->options & TAR_GNU))
	{
		/* GNU-style long name (no file name length limit) */
		t->th_buf.gnu_longname = strdup(pathname);
		strncpy(t->th_buf.name, t->th_buf.gnu_longname, T_NAMELEN);
	}
	else if (pathname_len >= T_NAMELEN)
	{
		/* POSIX-style prefix field:
		 *   The maximum length of a file name is limited to 256 characters,
		 *   provided that the file name can be split at a directory separator
		 *   in two parts.  The first part being at most 155 bytes long and
		 *   the second part being at most 100 bytes long.  So, in most cases
		 *   the maximum file name length will be shorter than 256 characters.
		 */
		char tail_path[T_NAMELEN + 1];
		tmp = strchr(&(pathname[pathname_len - T_NAMELEN]), '/');
		if (tmp == NULL)
		{
			printf("!!! '/' not found in \"%s\"\n", pathname);
			return;
		}
		snprintf(tail_path, T_NAMELEN + 1, "%s%s", &tmp[1], suffix);
		strncpy(t->th_buf.name, tail_path, T_NAMELEN);

		/*
		 * first part, max = 155 == sizeof(t->th_buf.prefix) , include NULL if it fits
		 * trailing '/' is added during decode: decode.c/th_get_pathname()
		 */
		if (tmp - pathname >= 155) {
			strncpy(t->th_buf.prefix, pathname, 155);
		} else {
			snprintf(t->th_buf.prefix, (tmp - pathname + 1), "%s", pathname);
		}
	}
	else {
		/* any short name for all formats, or classic tar format (99 chars max) */
		snprintf(t->th_buf.name, T_NAMELEN, "%s%s", pathname, suffix);
	}

#ifdef DEBUG
	puts("returning from th_set_path()...");
#endif
}


/* encode link path */
void
th_set_link(TAR *t, const char *linkname)
{
#ifdef DEBUG
	printf("==> th_set_link(th, linkname=\"%s\")\n", linkname);
#endif

	if (strlen(linkname) >= T_NAMELEN && (t->options & TAR_GNU))
	{
		/* --format=gnu: GNU-style long name (no file name length limit) */
		t->th_buf.gnu_longlink = strdup(linkname);
		strcpy(t->th_buf.linkname, "././@LongLink");
	}
	else if (strlen(linkname) >= T_NAMELEN)
	{
		/* --format=ustar: 100 chars max limit for symbolic links */
		strncpy(t->th_buf.linkname, linkname, T_NAMELEN);
		if (t->th_buf.gnu_longlink != NULL)
			free(t->th_buf.gnu_longlink);
		t->th_buf.gnu_longlink = NULL;
	} else {
		/* all short links or v7 tar format: The maximum length of a symbolic link name is limited to 99 characters */
		snprintf(t->th_buf.linkname, T_NAMELEN, "%s", linkname);
		if (t->th_buf.gnu_longlink != NULL)
			free(t->th_buf.gnu_longlink);
		t->th_buf.gnu_longlink = NULL;
	}
}


/* encode device info */
void
th_set_device(TAR *t, dev_t device)
{
#ifdef DEBUG
	printf("th_set_device(): major = %d, minor = %d\n",
	       major(device), minor(device));
#endif
	int_to_oct(major(device), t->th_buf.devmajor, 8);
	int_to_oct(minor(device), t->th_buf.devminor, 8);
}


/* encode user info */
void
th_set_user(TAR *t, uid_t uid)
{
	struct passwd *pw;

	if (!(t->options & TAR_USE_NUMERIC_ID)) {
		pw = getpwuid(uid);
		if (pw != NULL)
			strlcpy(t->th_buf.uname, pw->pw_name, sizeof(t->th_buf.uname));
	}

	int_to_oct(uid, t->th_buf.uid, 8);
}


/* encode group info */
void
th_set_group(TAR *t, gid_t gid)
{
	struct group *gr;

	if (!(t->options & TAR_USE_NUMERIC_ID)) {
		gr = getgrgid(gid);
		if (gr != NULL)
			strlcpy(t->th_buf.gname, gr->gr_name, sizeof(t->th_buf.gname));
	}

	int_to_oct(gid, t->th_buf.gid, 8);
}


/* encode file mode */
void
th_set_mode(TAR *t, mode_t fmode)
{
	if (S_ISSOCK(fmode))
	{
		fmode &= ~S_IFSOCK;
		fmode |= S_IFIFO;
	}
	int_to_oct(fmode, (t)->th_buf.mode, 8);
}


void
th_set_from_stat(TAR *t, struct stat *s)
{
	th_set_type(t, s->st_mode);
	if (S_ISCHR(s->st_mode) || S_ISBLK(s->st_mode))
		th_set_device(t, s->st_rdev);
	th_set_user(t, s->st_uid);
	th_set_group(t, s->st_gid);
	th_set_mode(t, s->st_mode);
	th_set_mtime(t, s->st_mtime);
	if (S_ISREG(s->st_mode))
		th_set_size(t, s->st_size);
	else
		th_set_size(t, 0);
}