]> git.saurik.com Git - apple/system_cmds.git/blob - dirhelper.tproj/dirhelper.c
02165465d4864f54103d48bb043c7af2bdd0f563
[apple/system_cmds.git] / dirhelper.tproj / dirhelper.c
1 /*
2 * Copyright (c) 2006-2007, 2010 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/kauth.h>
34 #include <sys/stat.h>
35 #include <sys/sysctl.h>
36 #include <sys/time.h>
37
38 #include <mach/mach.h>
39 #include <mach/mach_error.h>
40 #include <servers/bootstrap.h>
41
42 #include <bsm/libbsm.h>
43
44 #include <asl.h>
45 #include <membership.h>
46 #include <launch.h>
47 #include <dirhelper_priv.h>
48
49 #include "dirhelper.h"
50 #include "dirhelperServer.h"
51 /*
52 * Uncomment the next line to define BOOTDEBUG, which will write timing
53 * info for clean_directories() to a file, /Debug.
54 */
55 //#define BOOTDEBUG
56 #ifdef BOOTDEBUG
57 #include <mach/mach_time.h>
58 #endif //BOOTDEBUG
59
60 // globals for idle exit
61 struct idle_globals {
62 mach_port_t mp;
63 long timeout;
64 struct timeval lastmsg;
65 };
66
67 struct idle_globals idle_globals;
68
69 // argument structure for clean_thread
70 struct clean_args {
71 const char** dirs;
72 int machineBoot;
73 };
74
75 void* idle_thread(void* param __attribute__((unused)));
76
77 int file_check(const char* path, int mode, int uid, int gid, uid_t* owner, gid_t* group);
78 #define is_file(x) file_check((x), S_IFREG, -1, -1, NULL, NULL)
79 #define is_directory(x) file_check((x), S_IFDIR, -1, -1, NULL, NULL)
80 #define is_directory_get_owner_group(x,o,g) file_check((x), S_IFDIR, -1, -1, (o), (g))
81 #define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0, NULL, NULL)
82
83 int is_safeboot(void);
84
85 void clean_files_older_than(const char* path, time_t when);
86 void clean_directories(const char* names[], int);
87
88 kern_return_t
89 do___dirhelper_create_user_local(
90 mach_port_t server_port __attribute__((unused)),
91 audit_token_t au_tok)
92 {
93 int res = 0;
94 uid_t euid;
95 gid_t gid = 0;
96 struct passwd* pwd = NULL;
97
98 gettimeofday(&idle_globals.lastmsg, NULL);
99
100 audit_token_to_au32(au_tok,
101 NULL, // audit uid
102 &euid, // euid
103 NULL, // egid
104 NULL, // ruid
105 NULL, // rgid
106 NULL, // remote_pid
107 NULL, // asid
108 NULL); // aud_tid_t
109
110 // Look-up the primary gid of the user. We'll use this for chown(2)
111 // so that the created directory is owned by a group that the user
112 // belongs to, avoiding warnings if files are moved outside this dir.
113 pwd = getpwuid(euid);
114 if (pwd) gid = pwd->pw_gid;
115
116 do { // begin block
117 char path[PATH_MAX];
118 char *next;
119
120 if (__user_local_dirname(euid, DIRHELPER_USER_LOCAL, path, sizeof(path)) == NULL) {
121 asl_log(NULL, NULL, ASL_LEVEL_ERR,
122 "__user_local_dirname: %s", strerror(errno));
123 break;
124 }
125
126 // All dirhelper directories are now at the same level, so
127 // we need to remove the DIRHELPER_TOP_STR suffix to get the
128 // parent directory.
129 path[strlen(path) - (sizeof(DIRHELPER_TOP_STR) - 1)] = 0;
130
131 //
132 // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory
133 // in path, ignoring failure if it already exists.
134 // 2. Change ownership of directory to the user.
135 //
136 next = path + strlen(VAR_FOLDERS_PATH);
137 while ((next = strchr(next, '/')) != NULL) {
138 *next = 0; // temporarily truncate
139 res = mkdir(path, 0755);
140 if (res != 0 && errno != EEXIST) {
141 asl_log(NULL, NULL, ASL_LEVEL_ERR,
142 "mkdir(%s): %s", path, strerror(errno));
143 break;
144 }
145 *next++ = '/'; // restore the slash and increment
146 }
147 if(next || res) // an error occurred
148 break;
149 res = chown(path, euid, gid);
150 if (res != 0) {
151 asl_log(NULL, NULL, ASL_LEVEL_ERR,
152 "chown(%s): %s", path, strerror(errno));
153 }
154 } while(0); // end block
155 return KERN_SUCCESS;
156 }
157
158 kern_return_t
159 do___dirhelper_idle_exit(
160 mach_port_t server_port __attribute__((unused)),
161 audit_token_t au_tok __attribute__((unused))) {
162
163 struct timeval now;
164 gettimeofday(&now, NULL);
165 long delta = now.tv_sec - idle_globals.lastmsg.tv_sec;
166 if (delta >= idle_globals.timeout) {
167 asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
168 "idle exit after %ld seconds", delta);
169 exit(EXIT_SUCCESS);
170 }
171
172 return KERN_SUCCESS;
173 }
174
175 void*
176 idle_thread(void* param __attribute__((unused))) {
177 for(;;) {
178 struct timeval now;
179 gettimeofday(&now, NULL);
180 long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec);
181 if (delta < idle_globals.timeout) {
182 // sleep for remainder of timeout
183 sleep(idle_globals.timeout - delta);
184 } else {
185 // timeout has elapsed, attempt to idle exit
186 __dirhelper_idle_exit(idle_globals.mp);
187 }
188 }
189 return NULL;
190 }
191
192 // If when == 0, all files are removed. Otherwise, only regular files that were both created _and_ last modified before `when`.
193 void
194 clean_files_older_than(const char* path, time_t when) {
195 FTS* fts;
196
197 char* path_argv[] = { (char*)path, NULL };
198 fts = fts_open(path_argv, FTS_PHYSICAL | FTS_XDEV, NULL);
199 if (fts) {
200 FTSENT* ent;
201 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning " VAR_FOLDERS_PATH "%s", path);
202 while ((ent = fts_read(fts))) {
203 switch(ent->fts_info) {
204 case FTS_F:
205 case FTS_DEFAULT:
206 if (when == 0) {
207 #if DEBUG
208 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
209 #endif
210 (void)unlink(ent->fts_path);
211 } else if (S_ISREG(ent->fts_statp->st_mode) && (ent->fts_statp->st_birthtime < when) && (ent->fts_statp->st_atime < when)) {
212 int fd = open(ent->fts_path, O_RDONLY | O_NONBLOCK);
213 if (fd != -1) {
214 // Obtain an exclusive lock so
215 // that we can avoid a race with other processes
216 // attempting to open or modify the file.
217 int res = flock(fd, LOCK_EX | LOCK_NB);
218 if (res == 0) {
219 struct stat sb;
220 res = fstat(fd, &sb);
221 if ((res == 0) && (sb.st_birthtime < when) && (sb.st_atime < when)) {
222 #if DEBUG
223 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
224 #endif
225 (void)unlink(ent->fts_path);
226 }
227 (void)flock(fd, LOCK_UN);
228 }
229 close(fd);
230 }
231 }
232 break;
233
234 case FTS_SL:
235 case FTS_SLNONE:
236 if (when == 0) {
237 #if DEBUG
238 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
239 #endif
240 (void)unlink(ent->fts_path);
241 }
242 break;
243
244 case FTS_DP:
245 if (when == 0) {
246 #if DEBUG
247 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "rmdir(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
248 #endif
249 (void)rmdir(ent->fts_path);
250 }
251 break;
252
253 case FTS_ERR:
254 case FTS_NS:
255 asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", ent->fts_path, strerror(ent->fts_errno));
256 break;
257
258 default:
259 break;
260 }
261 }
262 fts_close(fts);
263 } else {
264 asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", path, strerror(errno));
265 }
266 }
267
268 int
269 file_check(const char* path, int mode, int uid, int gid, uid_t* owner, gid_t* group) {
270 int check = 1;
271 struct stat sb;
272 if (lstat(path, &sb) == 0) {
273 check = check && ((sb.st_mode & S_IFMT) == mode);
274 check = check && ((sb.st_uid == (uid_t)uid) || uid == -1);
275 check = check && ((sb.st_gid == (gid_t)gid) || gid == -1);
276 if (check) {
277 if (owner) *owner = sb.st_uid;
278 if (group) *group = sb.st_gid;
279 }
280 } else {
281 if (errno != ENOENT) {
282 /* This will print a shorter path after chroot() */
283 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
284 }
285 check = 0;
286 }
287 return check;
288 }
289
290 int
291 is_safeboot(void) {
292 uint32_t sb = 0;
293 size_t sbsz = sizeof(sb);
294
295 if (sysctlbyname("kern.safeboot", &sb, &sbsz, NULL, 0) != 0) {
296 return 0;
297 } else {
298 return (int)sb;
299 }
300 }
301
302 void *
303 clean_thread(void *a) {
304 struct clean_args* args = (struct clean_args*)a;
305 DIR* d;
306 time_t when = 0;
307 int i;
308
309 if (!args->machineBoot) {
310 struct timeval now;
311 long days = 3;
312 const char* str = getenv("CLEAN_FILES_OLDER_THAN_DAYS");
313 if (str) {
314 days = strtol(str, NULL, 0);
315 }
316 (void)gettimeofday(&now, NULL);
317 for (i = 0; args->dirs[i]; i++)
318 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s older than %ld days", args->dirs[i], days);
319
320 when = now.tv_sec - (days * 60 * 60 * 24);
321 }
322
323 // Look up the boot time
324 struct timespec boottime;
325 size_t len = sizeof(boottime);
326 if (sysctlbyname("kern.boottime", &boottime, &len, NULL, 0) == -1) {
327 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", "sysctl kern.boottime", strerror(errno));
328 return NULL;
329 }
330
331 if (!is_root_wheel_directory(VAR_FOLDERS_PATH)) {
332 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, "invalid ownership");
333 return NULL;
334 }
335
336 if (chroot(VAR_FOLDERS_PATH)) {
337 asl_log(NULL, NULL, ASL_LEVEL_ERR, "chroot(%s) failed: %s",
338 VAR_FOLDERS_PATH, strerror(errno));
339 }
340 chdir("/");
341 if ((d = opendir("/"))) {
342 struct dirent* e;
343 char path[PATH_MAX];
344
345 // /var/folders/*
346 while ((e = readdir(d))) {
347 if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
348
349 snprintf(path, sizeof(path), "%s%s", "/", e->d_name);
350 if (is_root_wheel_directory(path)) {
351 DIR* d2 = opendir(path);
352 if (d2) {
353 struct dirent* e2;
354
355 // /var/folders/*/*
356 while ((e2 = readdir(d2))) {
357 char dirbuf[PATH_MAX];
358 uid_t owner;
359 gid_t group;
360 if (strcmp(e2->d_name, ".") == 0 || strcmp(e2->d_name, "..") == 0) continue;
361 snprintf(dirbuf, sizeof(dirbuf),
362 "%s/%s", path, e2->d_name);
363 if (!is_directory_get_owner_group(dirbuf, &owner, &group)) continue;
364 if (pthread_setugid_np(owner, group) != 0) {
365 asl_log(NULL, NULL, ASL_LEVEL_ERR,
366 "skipping %s: pthread_setugid_np(%u, %u): %s",
367 dirbuf, owner, group, strerror(errno));
368 continue;
369 }
370 for (i = 0; args->dirs[i]; i++) {
371 const char *name = args->dirs[i];
372 snprintf(dirbuf, sizeof(dirbuf),
373 "%s/%s/%s", path, e2->d_name, name);
374 if (is_directory(dirbuf)) {
375 // at boot time we clean all files,
376 // otherwise only clean regular files.
377 clean_files_older_than(dirbuf, when);
378 }
379 }
380 if (pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) != 0) {
381 asl_log(NULL, NULL, ASL_LEVEL_ERR,
382 "%s: pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE): %s",
383 dirbuf, strerror(errno));
384 }
385 }
386 closedir(d2);
387 } else {
388 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
389 }
390 }
391 }
392
393 closedir(d);
394 } else {
395 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, strerror(errno));
396 }
397 return NULL;
398 }
399
400 void
401 clean_directories(const char* dirs[], int machineBoot) {
402 struct clean_args args;
403 pthread_t t;
404 int ret;
405 #ifdef BOOTDEBUG
406 double ratio;
407 struct mach_timebase_info info;
408 uint64_t begin, end;
409 FILE *debug;
410
411 mach_timebase_info(&info);
412 ratio = (double)info.numer / ((double)info.denom * NSEC_PER_SEC);
413 begin = mach_absolute_time();
414 if((debug = fopen("/Debug", "a")) != NULL) {
415 fprintf(debug, "clean_directories: machineBoot=%d\n", machineBoot);
416 }
417 #endif //BOOTDEBUG
418
419 args.dirs = dirs;
420 args.machineBoot = machineBoot;
421 ret = pthread_create(&t, NULL, clean_thread, &args);
422 if (ret == 0) {
423 ret = pthread_join(t, NULL);
424 if (ret) {
425 asl_log(NULL, NULL, ASL_LEVEL_ERR, "clean_directories: pthread_join: %s",
426 strerror(ret));
427 }
428 } else {
429 asl_log(NULL, NULL, ASL_LEVEL_ERR, "clean_directories: pthread_create: %s",
430 strerror(ret));
431 }
432 #ifdef BOOTDEBUG
433 end = mach_absolute_time();
434 if(debug) {
435 fprintf(debug, "clean_directories: %f secs\n", ratio * (end - begin));
436 fclose(debug);
437 }
438 #endif //BOOTDEBUG
439 }
440
441 int
442 main(int argc, char* argv[]) {
443 mach_msg_size_t mxmsgsz = MAX_TRAILER_SIZE;
444 kern_return_t kr;
445 long idle_timeout = 30; // default 30 second timeout
446
447 #ifdef BOOTDEBUG
448 {
449 FILE *debug;
450 int i;
451 if((debug = fopen("/Debug", "a")) != NULL) {
452 for(i = 0; i < argc; i++) {
453 fprintf(debug, " %s", argv[i]);
454 }
455 fputc('\n', debug);
456 fclose(debug);
457 }
458 }
459 #endif //BOOTDEBUG
460 // Clean up TemporaryItems directory when launched at boot.
461 // It is safe to clean all file types at this time.
462 if (argc > 1 && strcmp(argv[1], "-machineBoot") == 0) {
463 const char *dirs[5];
464 int i = 0;
465 dirs[i++] = DIRHELPER_TEMP_STR;
466 dirs[i++] = "TemporaryItems";
467 dirs[i++] = "Cleanup At Startup";
468 if (is_safeboot()) {
469 dirs[i++] = DIRHELPER_CACHE_STR;
470 }
471 dirs[i] = NULL;
472 clean_directories(dirs, 1);
473 exit(EXIT_SUCCESS);
474 } else if (argc > 1 && strcmp(argv[1], "-cleanTemporaryItems") == 0) {
475 const char *dirs[] = {
476 DIRHELPER_TEMP_STR,
477 "TemporaryItems",
478 NULL
479 };
480 clean_directories(dirs, 0);
481 exit(EXIT_SUCCESS);
482 } else if (argc > 1) {
483 exit(EXIT_FAILURE);
484 }
485
486 launch_data_t config = NULL, checkin = NULL;
487 checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN);
488 config = launch_msg(checkin);
489 if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) {
490 asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed");
491 exit(EXIT_FAILURE);
492 }
493
494 launch_data_t tmv;
495 tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT);
496 if (tmv) {
497 idle_timeout = launch_data_get_integer(tmv);
498 asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
499 "idle timeout set: %ld seconds", idle_timeout);
500 }
501
502 launch_data_t svc;
503 svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES);
504 if (!svc) {
505 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services");
506 exit(EXIT_FAILURE);
507 }
508
509 svc = launch_data_dict_lookup(svc, DIRHELPER_BOOTSTRAP_NAME);
510 if (!svc) {
511 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s",
512 DIRHELPER_BOOTSTRAP_NAME);
513 exit(EXIT_FAILURE);
514 }
515
516 mach_port_t mp = launch_data_get_machport(svc);
517 if (mp == MACH_PORT_NULL) {
518 asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s",
519 DIRHELPER_BOOTSTRAP_NAME);
520 exit(EXIT_FAILURE);
521 }
522
523 // insert a send right so we can send our idle exit message
524 kr = mach_port_insert_right(mach_task_self(), mp, mp,
525 MACH_MSG_TYPE_MAKE_SEND);
526 if (kr != KERN_SUCCESS) {
527 asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s",
528 mach_error_string(kr));
529 exit(EXIT_FAILURE);
530 }
531
532 // spawn a thread for our idle timeout
533 pthread_t thread;
534 idle_globals.mp = mp;
535 idle_globals.timeout = idle_timeout;
536 gettimeofday(&idle_globals.lastmsg, NULL);
537 pthread_create(&thread, NULL, &idle_thread, NULL);
538
539 // look to see if we have any messages queued. if not, assume
540 // we were launched because of the calendar interval, and attempt
541 // to clean the temporary items.
542 mach_msg_type_number_t status_count = MACH_PORT_RECEIVE_STATUS_COUNT;
543 mach_port_status_t status;
544 kr = mach_port_get_attributes(mach_task_self(), mp,
545 MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_count);
546 if (kr == KERN_SUCCESS && status.mps_msgcount == 0) {
547 const char *dirs[] = {
548 DIRHELPER_TEMP_STR,
549 "TemporaryItems",
550 NULL
551 };
552 clean_directories(dirs, 0);
553 exit(EXIT_SUCCESS);
554 }
555
556 // main event loop
557
558 kr = mach_msg_server(dirhelper_server, mxmsgsz, mp,
559 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
560 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0));
561 if (kr != KERN_SUCCESS) {
562 asl_log(NULL, NULL, ASL_LEVEL_ERR,
563 "mach_msg_server(mp): %s", mach_error_string(kr));
564 exit(EXIT_FAILURE);
565 }
566
567 exit(EXIT_SUCCESS);
568 }