]> git.saurik.com Git - apple/system_cmds.git/blame - dirhelper.tproj/dirhelper.c
system_cmds-496.tar.gz
[apple/system_cmds.git] / dirhelper.tproj / dirhelper.c
CommitLineData
34d340d7
A
1/*
2 * Copyright (c) 2006-2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <stdbool.h>
24#include <stdlib.h>
25#include <stdio.h>
26#include <dirent.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <fts.h>
30#include <pthread.h>
31#include <pwd.h>
32#include <unistd.h>
33#include <sys/stat.h>
34#include <sys/sysctl.h>
35#include <sys/time.h>
36
37#include <mach/mach.h>
38#include <mach/mach_error.h>
39#include <servers/bootstrap.h>
40
41#include <bsm/libbsm.h>
42
43#include <asl.h>
44#include <membership.h>
45#include <launch.h>
46#include <dirhelper_priv.h>
47
916eb79e 48#include "dirhelper_server.h"
34d340d7
A
49
50// globals for idle exit
51struct idle_globals {
52 mach_port_t mp;
53 long timeout;
54 struct timeval lastmsg;
55};
56
57struct idle_globals idle_globals;
58
59void* idle_thread(void* param __attribute__((unused)));
60
61int file_check(const char* path, int mode, int uid, int gid);
62#define is_file(x) file_check((x), S_IFREG, -1, -1)
63#define is_directory(x) file_check((x), S_IFDIR, -1, -1)
64#define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0)
65
66int is_safeboot(void);
67
68void clean_files_older_than(const char* path, time_t when);
ef8ad44b 69void clean_directories(const char* names[], int);
34d340d7
A
70
71kern_return_t
72do___dirhelper_create_user_local(
73 mach_port_t server_port __attribute__((unused)),
74 audit_token_t au_tok)
75{
76 int res = 0;
77 uid_t euid;
78 gid_t gid = 0;
79 struct passwd* pwd = NULL;
80
81 gettimeofday(&idle_globals.lastmsg, NULL);
82
83 audit_token_to_au32(au_tok,
84 NULL, // audit uid
85 &euid, // euid
86 NULL, // egid
87 NULL, // ruid
88 NULL, // rgid
89 NULL, // remote_pid
90 NULL, // asid
91 NULL); // aud_tid_t
92
93 // Look-up the primary gid of the user. We'll use this for chown(2)
94 // so that the created directory is owned by a group that the user
95 // belongs to, avoiding warnings if files are moved outside this dir.
96 pwd = getpwuid(euid);
97 if (pwd) gid = pwd->pw_gid;
98
99 do { // begin block
100 char path[PATH_MAX];
101 char *next;
102
103 if (__user_local_dirname(euid, DIRHELPER_USER_LOCAL, path, sizeof(path)) == NULL) {
104 asl_log(NULL, NULL, ASL_LEVEL_ERR,
105 "__user_local_dirname: %s", strerror(errno));
106 break;
107 }
108
109 //
110 // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory
111 // in path, ignoring failure if it already exists.
112 // 2. Change ownership of directory to the user.
113 //
114 next = path + strlen(VAR_FOLDERS_PATH);
115 while ((next = strchr(next, '/')) != NULL) {
116 *next = 0; // temporarily truncate
117 res = mkdir(path, 0755);
118 if (res != 0 && errno != EEXIST) {
119 asl_log(NULL, NULL, ASL_LEVEL_ERR,
120 "mkdir(%s): %s", path, strerror(errno));
121 break;
122 }
123 *next++ = '/'; // restore the slash and increment
124 }
125 if(next || res) // an error occurred
126 break;
127 res = chown(path, euid, gid);
128 if (res != 0) {
129 asl_log(NULL, NULL, ASL_LEVEL_ERR,
130 "chown(%s): %s", path, strerror(errno));
131 }
132 } while(0); // end block
133 return KERN_SUCCESS;
134}
135
136kern_return_t
137do___dirhelper_idle_exit(
138 mach_port_t server_port __attribute__((unused)),
139 audit_token_t au_tok __attribute__((unused))) {
140
141 struct timeval now;
142 gettimeofday(&now, NULL);
143 long delta = now.tv_sec - idle_globals.lastmsg.tv_sec;
144 if (delta >= idle_globals.timeout) {
145 asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
146 "idle exit after %ld seconds", delta);
147 exit(EXIT_SUCCESS);
148 }
149
150 return KERN_SUCCESS;
151}
152
153void*
154idle_thread(void* param __attribute__((unused))) {
155 for(;;) {
156 struct timeval now;
157 gettimeofday(&now, NULL);
158 long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec);
159 if (delta < idle_globals.timeout) {
160 // sleep for remainder of timeout
161 sleep(idle_globals.timeout - delta);
162 } else {
163 // timeout has elapsed, attempt to idle exit
164 __dirhelper_idle_exit(idle_globals.mp);
165 }
166 }
167 return NULL;
168}
169
170// If when == 0, all files are removed. Otherwise, only regular files older than when.
171void
172clean_files_older_than(const char* path, time_t when) {
173 FTS* fts;
174
175 char* path_argv[] = { (char*)path, NULL };
176 fts = fts_open(path_argv, FTS_PHYSICAL | FTS_XDEV, NULL);
177 if (fts) {
178 FTSENT* ent;
ef8ad44b 179 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning " VAR_FOLDERS_PATH "%s", path);
34d340d7
A
180 while ((ent = fts_read(fts))) {
181 switch(ent->fts_info) {
182 case FTS_F:
183 case FTS_DEFAULT:
184 // Unlink the file if it has not been accessed since
185 // the specified time. Obtain an exclusive lock so
186 // that we can avoid a race with other processes
187 // attempting to open the file.
188 if (when == 0) {
ef8ad44b
A
189#if DEBUG
190 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
191#endif
34d340d7
A
192 (void)unlink(ent->fts_path);
193 } else if (S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_atime < when) {
194 int fd = open(ent->fts_path, O_RDONLY | O_NONBLOCK);
195 if (fd != -1) {
196 int res = flock(fd, LOCK_EX | LOCK_NB);
197 if (res == 0) {
198 struct stat sb;
199 res = fstat(fd, &sb);
200 if (res == 0 && sb.st_atime < when) {
ef8ad44b
A
201#if DEBUG
202 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
203#endif
34d340d7
A
204 (void)unlink(ent->fts_path);
205 }
206 (void)flock(fd, LOCK_UN);
207 }
208 close(fd);
209 }
210 }
211 break;
212
213 case FTS_SL:
214 case FTS_SLNONE:
215 if (when == 0) {
ef8ad44b
A
216#if DEBUG
217 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
218#endif
34d340d7
A
219 (void)unlink(ent->fts_path);
220 }
221 break;
222
223 case FTS_DP:
224 if (when == 0) {
ef8ad44b
A
225#if DEBUG
226 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "rmdir(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
227#endif
34d340d7
A
228 (void)rmdir(ent->fts_path);
229 }
230 break;
231
232 case FTS_ERR:
233 case FTS_NS:
ef8ad44b 234 asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", ent->fts_path, strerror(ent->fts_errno));
34d340d7
A
235 break;
236
237 default:
238 break;
239 }
240 }
241 fts_close(fts);
242 } else {
ef8ad44b 243 asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", path, strerror(errno));
34d340d7
A
244 }
245}
246
247int
248file_check(const char* path, int mode, int uid, int gid) {
249 int check = 1;
250 struct stat sb;
251 if (lstat(path, &sb) == 0) {
252 check = check && ((sb.st_mode & S_IFMT) == mode);
253 check = check && ((sb.st_uid == (uid_t)uid) || uid == -1);
254 check = check && ((sb.st_gid == (gid_t)gid) || gid == -1);
255 } else {
256 if (errno != ENOENT) {
ef8ad44b 257 /* This will print a shorter path after chroot() */
34d340d7
A
258 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
259 }
260 check = 0;
261 }
262 return check;
263}
264
265int
266is_safeboot(void) {
267 uint32_t sb = 0;
268 size_t sbsz = sizeof(sb);
269
270 if (sysctlbyname("kern.safeboot", &sb, &sbsz, NULL, 0) != 0) {
271 return 0;
272 } else {
273 return (int)sb;
274 }
275}
276
277void
ef8ad44b 278clean_directories(const char* dirs[], int machineBoot) {
34d340d7
A
279 DIR* d;
280 time_t when = 0;
ef8ad44b 281 int i;
34d340d7
A
282
283 if (!machineBoot) {
284 struct timeval now;
285 long days = 3;
286 const char* str = getenv("CLEAN_FILES_OLDER_THAN_DAYS");
287 if (str) {
288 days = strtol(str, NULL, 0);
289 }
290 (void)gettimeofday(&now, NULL);
ef8ad44b
A
291 for (i = 0; dirs[i]; i++)
292 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s older than %ld days", dirs[i], days);
34d340d7
A
293
294 when = now.tv_sec - (days * 60 * 60 * 24);
295 }
296
297 // Look up the boot time
298 struct timespec boottime;
299 size_t len = sizeof(boottime);
300 if (sysctlbyname("kern.boottime", &boottime, &len, NULL, 0) == -1) {
301 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", "sysctl kern.boottime", strerror(errno));
302 return;
303 }
304
305 if (!is_root_wheel_directory(VAR_FOLDERS_PATH)) {
306 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, "invalid ownership");
307 return;
308 }
ef8ad44b
A
309
310 if (chroot(VAR_FOLDERS_PATH)) {
311 asl_log(NULL, NULL, ASL_LEVEL_ERR, "chroot(%s) failed: %s",
312 VAR_FOLDERS_PATH, strerror(errno));
313 }
314 if ((d = opendir("/"))) {
34d340d7
A
315 struct dirent* e;
316 char path[PATH_MAX];
317
318 // /var/folders/*
319 while ((e = readdir(d))) {
320 if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
321
ef8ad44b 322 snprintf(path, sizeof(path), "%s%s", "/", e->d_name);
34d340d7
A
323 if (is_root_wheel_directory(path)) {
324 DIR* d2 = opendir(path);
325 if (d2) {
326 struct dirent* e2;
327
328 // /var/folders/*/*
329 while ((e2 = readdir(d2))) {
330 char temporary_items[PATH_MAX];
331 if (strcmp(e2->d_name, ".") == 0 || strcmp(e2->d_name, "..") == 0) continue;
ef8ad44b
A
332 for (i = 0; dirs[i]; i++) {
333 const char *name = dirs[i];
334 snprintf(temporary_items, sizeof(temporary_items),
335 "%s/%s/%s", path, e2->d_name, name);
336 if (is_directory(temporary_items)) {
337 // at boot time we clean all files,
338 // otherwise only clean regular files.
339 clean_files_older_than(temporary_items, when);
340 }
34d340d7
A
341 }
342 }
34d340d7
A
343 closedir(d2);
344 } else {
345 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
346 }
347 }
348 }
349
350 closedir(d);
351 } else {
352 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, strerror(errno));
353 }
354}
355
356int
357main(int argc, char* argv[]) {
358 mach_msg_size_t mxmsgsz = MAX_TRAILER_SIZE;
359 kern_return_t kr;
360 long idle_timeout = 30; // default 30 second timeout
361
362 // Clean up TemporaryItems directory when launched at boot.
363 // It is safe to clean all file types at this time.
364 if (argc > 1 && strcmp(argv[1], "-machineBoot") == 0) {
ef8ad44b
A
365 const char *dirs[5];
366 int i = 0;
367 dirs[i++] = DIRHELPER_TEMP_STR;
368 dirs[i++] = "TemporaryItems";
369 dirs[i++] = "Cleanup At Startup";
370 if (is_safeboot()) {
371 dirs[i++] = DIRHELPER_CACHE_STR;
372 }
373 dirs[i] = NULL;
374 clean_directories(dirs, 1);
34d340d7
A
375 exit(EXIT_SUCCESS);
376 } else if (argc > 1 && strcmp(argv[1], "-cleanTemporaryItems") == 0) {
ef8ad44b
A
377 const char *dirs[] = {
378 DIRHELPER_TEMP_STR,
379 "TemporaryItems",
380 NULL
381 };
382 clean_directories(dirs, 0);
34d340d7
A
383 exit(EXIT_SUCCESS);
384 } else if (argc > 1) {
385 exit(EXIT_FAILURE);
386 }
387
388 launch_data_t config = NULL, checkin = NULL;
389 checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN);
390 config = launch_msg(checkin);
391 if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) {
392 asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed");
393 exit(EXIT_FAILURE);
394 }
395
396 launch_data_t tmv;
397 tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT);
398 if (tmv) {
399 idle_timeout = launch_data_get_integer(tmv);
400 asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
401 "idle timeout set: %ld seconds", idle_timeout);
402 }
403
404 launch_data_t svc;
405 svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES);
406 if (!svc) {
407 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services");
408 exit(EXIT_FAILURE);
409 }
410
411 svc = launch_data_dict_lookup(svc, DIRHELPER_BOOTSTRAP_NAME);
412 if (!svc) {
413 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s",
414 DIRHELPER_BOOTSTRAP_NAME);
415 exit(EXIT_FAILURE);
416 }
417
418 mach_port_t mp = launch_data_get_machport(svc);
419 if (mp == MACH_PORT_NULL) {
420 asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s",
421 DIRHELPER_BOOTSTRAP_NAME);
422 exit(EXIT_FAILURE);
423 }
424
425 // insert a send right so we can send our idle exit message
426 kr = mach_port_insert_right(mach_task_self(), mp, mp,
427 MACH_MSG_TYPE_MAKE_SEND);
428 if (kr != KERN_SUCCESS) {
429 asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s",
430 mach_error_string(kr));
431 exit(EXIT_FAILURE);
432 }
433
434 // spawn a thread for our idle timeout
435 pthread_t thread;
436 idle_globals.mp = mp;
437 idle_globals.timeout = idle_timeout;
438 gettimeofday(&idle_globals.lastmsg, NULL);
439 pthread_create(&thread, NULL, &idle_thread, NULL);
440
441 // look to see if we have any messages queued. if not, assume
442 // we were launched because of the calendar interval, and attempt
443 // to clean the temporary items.
444 mach_msg_type_number_t status_count = MACH_PORT_RECEIVE_STATUS_COUNT;
445 mach_port_status_t status;
446 kr = mach_port_get_attributes(mach_task_self(), mp,
447 MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_count);
448 if (kr == KERN_SUCCESS && status.mps_msgcount == 0) {
ef8ad44b
A
449 const char *dirs[] = {
450 DIRHELPER_TEMP_STR,
451 "TemporaryItems",
452 NULL
453 };
454 clean_directories(dirs, 0);
455 exit(EXIT_SUCCESS);
34d340d7
A
456 }
457
458 // main event loop
459
460 kr = mach_msg_server(dirhelper_server, mxmsgsz, mp,
461 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
462 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0));
463 if (kr != KERN_SUCCESS) {
464 asl_log(NULL, NULL, ASL_LEVEL_ERR,
465 "mach_msg_server(mp): %s", mach_error_string(kr));
466 exit(EXIT_FAILURE);
467 }
468
469 exit(EXIT_SUCCESS);
470}