1 /* @(#)dirtime.c	1.34 18/08/31 Copyright 1988-2018 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static	UConst char sccsid[] =
5 	"@(#)dirtime.c	1.34 18/08/31 Copyright 1988-2018 J. Schilling";
6 #endif
7 /*
8  *	Copyright (c) 1988-2018 J. Schilling
9  */
10 /*
11  * The contents of this file are subject to the terms of the
12  * Common Development and Distribution License, Version 1.0 only
13  * (the "License").  You may not use this file except in compliance
14  * with the License.
15  *
16  * See the file CDDL.Schily.txt in this distribution for details.
17  * A copy of the CDDL is also available via the Internet at
18  * http://www.opensource.org/licenses/cddl1.txt
19  *
20  * When distributing Covered Code, include this CDDL HEADER in each
21  * file and include the License file CDDL.Schily.txt from this distribution.
22  */
23 
24 /*
25  * Save directories and its times on a stack and set the times, if the new name
26  * will not increase the depth of the directory stack.
27  * The final flush of the stack is caused by a zero length filename.
28  *
29  * A string will be sufficient for the names of the directory stack because
30  * all directories in a tree have a common prefix.  A counter for each
31  * occurence of a slash '/' is the index into the array of times for the
32  * directory stack. Directories with unknown times have atime.tv_nsec == -1.
33  *
34  * If the order of the files on tape is not in an order that find(1) will
35  * produce, this algorithm is not guaranteed to work. This is the case with
36  * tapes that have been created with the -r option or with the list= option.
37  *
38  * The only alternative would be saving all directory times and setting them
39  * at the end of an extract.
40  *
41  * NOTE: I am not shure if degenerate filenames will fool this algorithm.
42  */
43 #include <schily/types.h>	/* includes <sys/types.h> needed for mode_t */
44 #include <schily/stdio.h>
45 #include <schily/standard.h>
46 #include <schily/string.h>
47 #define	GT_COMERR		/* #define comerr gtcomerr */
48 #define	GT_ERROR		/* #define error gterror   */
49 #include <schily/schily.h>
50 #include "star.h"
51 #include "xutimes.h"
52 #include "checkerr.h"
53 #include "dirtime.h"
54 #include "starsubs.h"
55 #include "pathname.h"
56 
57 #ifdef DEBUG
58 extern	BOOL	debug;
59 #define	EDBG(a)	if (debug) error a
60 #else
61 #define	EDBG(a)
62 #endif
63 
64 /*
65  * Maximum depth of directory nesting depends on availabe memory.
66  */
67 LOCAL	pathstore_t	dirstack;
68 
69 #ifdef	SET_CTIME
70 #define	NT	3
71 LOCAL	struct timespec dottimes[NT] = { {-1, -1}, {-1, -1}, {-1, -1}};
72 #else
73 #define	NT	2
74 LOCAL	struct timespec dottimes[NT] = { -1, -1, -1, -1};
75 #endif
76 LOCAL	struct timespec badtime = { -1, -1};
77 
78 typedef	struct timespec	timev[NT];
79 
80 LOCAL	pathstore_t	dtps;
81 LOCAL	int		ndtimes;
82 LOCAL	timev		*dtimes;
83 
84 LOCAL	mode_t	dotmodes = _BAD_MODE;
85 
86 LOCAL	pathstore_t	dmps;
87 LOCAL	int		ndmodes;
88 LOCAL	mode_t		*dmodes;
89 
90 
91 LOCAL	BOOL	init_dirtimes	__PR((void));
92 LOCAL	BOOL	grow_dtimes	__PR((int n));
93 LOCAL	BOOL	grow_dmodes	__PR((int n));
94 EXPORT	void	sdirtimes	__PR((char *name, FINFO *info,
95 						BOOL do_times, BOOL do_mode));
96 EXPORT	void	sdirmode	__PR((char *name, mode_t mode));
97 LOCAL	void	dirtimes	__PR((char *name, struct timespec *tp,
98 								mode_t mode));
99 EXPORT	void	flushdirtimes	__PR((void));
100 LOCAL	void	flushdirstack	__PR((char *, char *, int));
101 LOCAL	void	setdirtime	__PR((char *, struct timespec *));
102 
103 LOCAL BOOL
init_dirtimes()104 init_dirtimes()
105 {
106 	/*
107 	 * First the path string
108 	 */
109 	if (init_pspace(PS_STDERR, &dirstack) < 0)
110 		return (FALSE);
111 	/*
112 	 * Now the time array
113 	 */
114 	if (!grow_dtimes(1))
115 		return (FALSE);
116 
117 	/*
118 	 * Now the mode array
119 	 */
120 	if (!grow_dmodes(1))
121 		return (FALSE);
122 
123 	return (TRUE);
124 }
125 
126 LOCAL BOOL
grow_dtimes(n)127 grow_dtimes(n)
128 	int	n;
129 {
130 	if ((n * sizeof (timev)) < ndtimes)
131 		return (TRUE);
132 
133 	/*
134 	 * Add 1 since "ndtimes" is the size of the array while "n" is the
135 	 * next array index to use.
136 	 */
137 	if (grow_pspace(PS_STDERR, &dtps, (n+1) * sizeof (timev)) < 0) {
138 		return (FALSE);
139 	}
140 
141 	dtimes = (timev *)dtps.ps_path;
142 	ndtimes = dtps.ps_size / sizeof (timev);
143 
144 	return (TRUE);
145 }
146 
147 LOCAL BOOL
grow_dmodes(n)148 grow_dmodes(n)
149 	int	n;
150 {
151 	if ((n * sizeof (mode_t)) < ndmodes)
152 		return (TRUE);
153 
154 	/*
155 	 * Add 1 since "ndmodes" is the size of the array while "n" is the
156 	 * next array index to use.
157 	 */
158 	if (grow_pspace(PS_STDERR, &dmps, (n+1) * sizeof (mode_t)) < 0) {
159 		return (FALSE);
160 	}
161 
162 	dmodes = (mode_t *)dmps.ps_path;
163 	ndmodes = dmps.ps_size / sizeof (mode_t);
164 
165 	return (TRUE);
166 }
167 
168 /*
169  * This is the standard method to enter time and mode values
170  * into the directory stack.
171  */
172 EXPORT void
sdirtimes(name,info,do_times,do_mode)173 sdirtimes(name, info, do_times, do_mode)
174 	char	*name;
175 	FINFO	*info;
176 	BOOL	do_times;
177 	BOOL	do_mode;
178 {
179 	struct timespec	tp[NT];
180 	mode_t		mode = _BAD_MODE;
181 
182 	if (do_times) {
183 		tp[0].tv_sec = info->f_atime;
184 		tp[0].tv_nsec = info->f_ansec;
185 
186 		tp[1].tv_sec = info->f_mtime;
187 		tp[1].tv_nsec = info->f_mnsec;
188 #ifdef	SET_CTIME
189 		tp[2].tv_sec = info->f_ctime;
190 		tp[2].tv_nsec = info->f_cnsec;
191 #endif
192 	} else {
193 		tp[0] = badtime;
194 		tp[1] = badtime;
195 #ifdef	SET_CTIME
196 		tp[2] = badtime;
197 #endif
198 	}
199 	if (do_mode) {
200 		mode = info->f_mode;
201 	}
202 	dirtimes(name, tp, mode);
203 }
204 
205 /*
206  * This is the method to enter mode values into the directory stack.
207  * It is only used to puch the umask value from _create_dirs().
208  */
209 EXPORT void
210 #ifdef	PROTOTYPES
sdirmode(char * name,mode_t mode)211 sdirmode(char *name, mode_t mode)
212 #else
213 sdirmode(name, mode)
214 	char	*name;
215 	mode_t	mode;
216 #endif
217 {
218 	struct timespec	tp[NT];
219 
220 	tp[0] = badtime;
221 	tp[1] = badtime;
222 #ifdef	SET_CTIME
223 	tp[2] = badtime;
224 #endif
225 	dirtimes(name, tp, mode);
226 }
227 
228 LOCAL void
229 #ifdef	PROTOTYPES
dirtimes(char * name,struct timespec tp[NT],mode_t mode)230 dirtimes(char *name, struct timespec tp[NT], mode_t mode)
231 #else
232 dirtimes(name, tp, mode)
233 	char		*name;
234 	struct timespec	tp[NT];
235 	mode_t		mode;
236 #endif
237 {
238 	register char	*dp = dirstack.ps_path;
239 	register char	*np = name;
240 	register int	idx = -1;
241 
242 	if (dp == NULL) {
243 		if (!init_dirtimes())
244 			return;
245 		dp = dirstack.ps_path;
246 	}
247 	EDBG(("dirtimes('%s', %s", name, tp ? ctime(&tp[1].tv_sec):"NULL\n"));
248 
249 	if (np[0] == '\0') {				/* final flush */
250 		if (dotmodes != _BAD_MODE) {
251 			EDBG(("setmode: '.' to 0%o\n", dotmodes));
252 			setdirmodes(".", dotmodes);
253 		}
254 		if (dottimes[0].tv_nsec != badtime.tv_nsec)
255 			setdirtime(".", dottimes);
256 		flushdirstack(dp, dp, -1);
257 		return;
258 	}
259 
260 	if ((np[0] == '.' && np[1] == '/' && np[2] == '\0') ||
261 				(np[0] == '.' && np[1] == '\0')) {
262 		dottimes[0] = tp[0];
263 		dottimes[1] = tp[1];
264 #ifdef	SET_CTIME
265 		dottimes[2] = tp[2];
266 #endif
267 		dotmodes = mode;
268 	} else {
269 		size_t	nlen;
270 
271 		nlen = strlen(np);
272 		if (nlen >= dirstack.ps_size) {
273 			if (set_pspace(PS_STDERR, &dirstack, nlen+1) < 0) {
274 				return;
275 			}
276 			dp = dirstack.ps_path;
277 		}
278 
279 		/*
280 		 * Find end of common part
281 		 */
282 		while (*dp == *np) {
283 			if (*dp == '\0')
284 				break;
285 			if (*dp++ == '/')
286 				++idx;
287 			np++;
288 		}
289 		/*
290 		 * Make sure that the ending '/' always stays in dirstack.
291 		 */
292 		if (*dp == '/' && *np == '\0') {
293 			dp++;
294 			++idx;
295 		}
296 		EDBG(("DIR: '%.*s' DP: '%s' NP: '%s' idx: %d\n",
297 				/* XXX Should not be > int */
298 				(int)(dp - dirstack.ps_path),
299 				dirstack.ps_path, dp, np, idx));
300 
301 		if (*dp) {
302 			/*
303 			 * New directory does not increase the depth of the
304 			 * directory stack. Flush all dirs below idx.
305 			 */
306 			flushdirstack(dirstack.ps_path, dp, idx);
307 		}
308 
309 		/*
310 		 * Put the new dir on the directory stack.
311 		 * First append the name component, then
312 		 * store times of "this" dir.
313 		 */
314 		while ((*dp = *np++) != '\0') {
315 			if (*dp++ == '/') {
316 				/*
317 				 * Disable times of unknown dirs.
318 				 */
319 				if (!grow_dtimes(++idx))
320 					return;
321 				if (!grow_dmodes(idx))
322 					return;
323 				EDBG(("zapping idx: %d\n", idx));
324 				dtimes[idx][0] = badtime;
325 				dmodes[idx] = _BAD_MODE;
326 			} else if (*np == '\0') {
327 				/*
328 				 * Make sure the dirname always ends with '/'.
329 				 */
330 				*dp++ = '/';
331 				*dp = '\0';
332 				idx++;
333 			}
334 		}
335 		if (tp) {
336 			if (!grow_dtimes(idx))
337 				return;
338 			if (!grow_dmodes(idx))
339 				return;
340 			EDBG(("set idx %d '%s'\n", idx, name));
341 			dtimes[idx][0] = tp[0];	/* overwrite last atime */
342 			dtimes[idx][1] = tp[1];	/* overwrite last mtime */
343 #ifdef	SET_CTIME
344 			dtimes[idx][2] = tp[2];	/* overwrite last ctime */
345 #endif
346 			dmodes[idx] = mode;
347 		}
348 	}
349 }
350 
351 /*
352  * Needed for on_comerr() as we cannot pass the needed parameters to dirtimes().
353  */
354 EXPORT void
flushdirtimes()355 flushdirtimes()
356 {
357 	dirtimes("", (struct timespec *)0, (mode_t)0);
358 }
359 
360 LOCAL void
flushdirstack(dirbase,dp,depth)361 flushdirstack(dirbase, dp, depth)
362 		char	*dirbase;
363 	register char	*dp;
364 	register int	depth;
365 {
366 	if (depth == -1 && dp == dirbase && dp[0] == '/') {
367 		/*
368 		 * Flush the root dir, avoid flushing "".
369 		 */
370 		while (*dp == '/')
371 			dp++;
372 		if (dmodes[++depth] != _BAD_MODE) {
373 			EDBG(("depth: %d setmode: '/' to 0%o\n",
374 							depth, dmodes[depth]));
375 			setdirmodes("/", dmodes[depth]);
376 		}
377 		if (dtimes[depth][0].tv_nsec != badtime.tv_nsec) {
378 			EDBG(("depth: %d ", depth));
379 			setdirtime("/", dtimes[depth]);
380 		}
381 	}
382 	/*
383 	 * The dirname always ends with a '/' (see above).
384 	 */
385 	while (*dp) {
386 		if (*dp++ == '/') {
387 			*--dp = '\0';	/* temporarily delete '/' */
388 			if (dmodes[++depth] != _BAD_MODE) {
389 				EDBG(("depth: %d setmode: '%s' to 0%o\n",
390 						depth, dirbase, dmodes[depth]));
391 				setdirmodes(dirbase, dmodes[depth]);
392 			}
393 			if (dtimes[depth][0].tv_nsec != badtime.tv_nsec) {
394 				EDBG(("depth: %d ", depth));
395 				setdirtime(dirbase, dtimes[depth]);
396 			}
397 			*dp++ = '/';	/* restore '/' */
398 		}
399 	}
400 }
401 
402 LOCAL void
setdirtime(name,tp)403 setdirtime(name, tp)
404 	char	*name;
405 	struct timespec	tp[NT];
406 {
407 	EDBG(("settime: '%s' to %s", name, ctime(&tp[1].tv_sec)));
408 	if (xutimes(name, tp, FALSE) < 0) {
409 		if (!errhidden(E_SETTIME, name)) {
410 			if (!errwarnonly(E_SETTIME, name))
411 				xstats.s_settime++;
412 			errmsg("Can't set time on '%s'.\n", name);
413 			(void) errabort(E_SETTIME, name, TRUE);
414 		}
415 	}
416 }
417