2 * Copyright (c) 2006-2007, 2010 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
33 #include <sys/kauth.h>
35 #include <sys/sysctl.h>
38 #include <mach/mach.h>
39 #include <mach/mach_error.h>
40 #include <servers/bootstrap.h>
42 #include <bsm/libbsm.h>
45 #include <membership.h>
47 #include <dirhelper_priv.h>
49 #include "dirhelper.h"
50 #include "dirhelperServer.h"
52 * Uncomment the next line to define BOOTDEBUG, which will write timing
53 * info for clean_directories() to a file, /Debug.
57 #include <mach/mach_time.h>
60 // globals for idle exit
64 struct timeval lastmsg
;
67 struct idle_globals idle_globals
;
69 // argument structure for clean_thread
75 void* idle_thread(void* param
__attribute__((unused
)));
76 void* clean_thread(void *);
78 int file_check(const char* path
, int mode
, int uid
, int gid
, uid_t
* owner
, gid_t
* group
);
79 #define is_file(x) file_check((x), S_IFREG, -1, -1, NULL, NULL)
80 #define is_directory(x) file_check((x), S_IFDIR, -1, -1, NULL, NULL)
81 #define is_directory_get_owner_group(x,o,g) file_check((x), S_IFDIR, -1, -1, (o), (g))
82 #define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0, NULL, NULL)
84 int is_safeboot(void);
86 void clean_files_older_than(const char* path
, time_t when
);
87 void clean_directories(const char* names
[], int);
90 do___dirhelper_create_user_local(
91 mach_port_t server_port
__attribute__((unused
)),
97 struct passwd
* pwd
= NULL
;
99 gettimeofday(&idle_globals
.lastmsg
, NULL
);
101 audit_token_to_au32(au_tok
,
111 // Look-up the primary gid of the user. We'll use this for chown(2)
112 // so that the created directory is owned by a group that the user
113 // belongs to, avoiding warnings if files are moved outside this dir.
114 pwd
= getpwuid(euid
);
115 if (pwd
) gid
= pwd
->pw_gid
;
121 if (__user_local_dirname(euid
, DIRHELPER_USER_LOCAL
, path
, sizeof(path
)) == NULL
) {
122 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
123 "__user_local_dirname: %s", strerror(errno
));
127 // All dirhelper directories are now at the same level, so
128 // we need to remove the DIRHELPER_TOP_STR suffix to get the
130 path
[strlen(path
) - (sizeof(DIRHELPER_TOP_STR
) - 1)] = 0;
133 // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory
134 // in path, ignoring failure if it already exists.
135 // 2. Change ownership of directory to the user.
137 next
= path
+ strlen(VAR_FOLDERS_PATH
);
138 while ((next
= strchr(next
, '/')) != NULL
) {
139 *next
= 0; // temporarily truncate
140 res
= mkdir(path
, 0755);
141 if (res
!= 0 && errno
!= EEXIST
) {
142 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
143 "mkdir(%s): %s", path
, strerror(errno
));
146 *next
++ = '/'; // restore the slash and increment
148 if(next
|| res
) // an error occurred
150 res
= chown(path
, euid
, gid
);
152 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
153 "chown(%s): %s", path
, strerror(errno
));
155 } while(0); // end block
160 do___dirhelper_idle_exit(
161 mach_port_t server_port
__attribute__((unused
)),
162 audit_token_t au_tok
__attribute__((unused
))) {
165 gettimeofday(&now
, NULL
);
166 long delta
= now
.tv_sec
- idle_globals
.lastmsg
.tv_sec
;
167 if (delta
>= idle_globals
.timeout
) {
168 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
169 "idle exit after %ld seconds", delta
);
177 idle_thread(void* param
__attribute__((unused
))) {
180 gettimeofday(&now
, NULL
);
181 long delta
= (now
.tv_sec
- idle_globals
.lastmsg
.tv_sec
);
182 if (delta
< idle_globals
.timeout
) {
183 // sleep for remainder of timeout
184 sleep((int)(idle_globals
.timeout
- delta
));
186 // timeout has elapsed, attempt to idle exit
187 __dirhelper_idle_exit(idle_globals
.mp
);
193 // If when == 0, all files are removed. Otherwise, only regular files that were both created _and_ last modified before `when`.
195 clean_files_older_than(const char* path
, time_t when
) {
198 char* path_argv
[] = { (char*)path
, NULL
};
199 fts
= fts_open(path_argv
, FTS_PHYSICAL
| FTS_XDEV
, NULL
);
202 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning " VAR_FOLDERS_PATH
"%s", path
);
203 while ((ent
= fts_read(fts
))) {
204 switch(ent
->fts_info
) {
209 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
211 (void)unlink(ent
->fts_path
);
212 } else if (S_ISREG(ent
->fts_statp
->st_mode
) && (ent
->fts_statp
->st_birthtime
< when
) && (ent
->fts_statp
->st_atime
< when
)) {
213 int fd
= open(ent
->fts_path
, O_RDONLY
| O_NONBLOCK
);
215 // Obtain an exclusive lock so
216 // that we can avoid a race with other processes
217 // attempting to open or modify the file.
218 int res
= flock(fd
, LOCK_EX
| LOCK_NB
);
221 res
= fstat(fd
, &sb
);
222 if ((res
== 0) && (sb
.st_birthtime
< when
) && (sb
.st_atime
< when
)) {
224 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
226 (void)unlink(ent
->fts_path
);
228 (void)flock(fd
, LOCK_UN
);
239 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
241 (void)unlink(ent
->fts_path
);
248 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "rmdir(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
250 (void)rmdir(ent
->fts_path
);
256 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, VAR_FOLDERS_PATH
"%s: %s", ent
->fts_path
, strerror(ent
->fts_errno
));
265 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, VAR_FOLDERS_PATH
"%s: %s", path
, strerror(errno
));
270 file_check(const char* path
, int mode
, int uid
, int gid
, uid_t
* owner
, gid_t
* group
) {
273 if (lstat(path
, &sb
) == 0) {
274 check
= check
&& ((sb
.st_mode
& S_IFMT
) == mode
);
275 check
= check
&& ((sb
.st_uid
== (uid_t
)uid
) || uid
== -1);
276 check
= check
&& ((sb
.st_gid
== (gid_t
)gid
) || gid
== -1);
278 if (owner
) *owner
= sb
.st_uid
;
279 if (group
) *group
= sb
.st_gid
;
282 if (errno
!= ENOENT
) {
283 /* This will print a shorter path after chroot() */
284 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
294 size_t sbsz
= sizeof(sb
);
296 if (sysctlbyname("kern.safeboot", &sb
, &sbsz
, NULL
, 0) != 0) {
304 clean_thread(void *a
) {
305 struct clean_args
* args
= (struct clean_args
*)a
;
310 if (!args
->machineBoot
) {
313 const char* str
= getenv("CLEAN_FILES_OLDER_THAN_DAYS");
315 days
= strtol(str
, NULL
, 0);
317 (void)gettimeofday(&now
, NULL
);
318 for (i
= 0; args
->dirs
[i
]; i
++)
319 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning %s older than %ld days", args
->dirs
[i
], days
);
321 when
= now
.tv_sec
- (days
* 60 * 60 * 24);
324 // Look up the boot time
325 struct timespec boottime
;
326 size_t len
= sizeof(boottime
);
327 if (sysctlbyname("kern.boottime", &boottime
, &len
, NULL
, 0) == -1) {
328 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", "sysctl kern.boottime", strerror(errno
));
332 if (!is_root_wheel_directory(VAR_FOLDERS_PATH
)) {
333 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, "invalid ownership");
337 if (chroot(VAR_FOLDERS_PATH
)) {
338 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "chroot(%s) failed: %s",
339 VAR_FOLDERS_PATH
, strerror(errno
));
342 if ((d
= opendir("/"))) {
347 while ((e
= readdir(d
))) {
348 if (strcmp(e
->d_name
, ".") == 0 || strcmp(e
->d_name
, "..") == 0) continue;
350 snprintf(path
, sizeof(path
), "%s%s", "/", e
->d_name
);
351 if (is_root_wheel_directory(path
)) {
352 DIR* d2
= opendir(path
);
357 while ((e2
= readdir(d2
))) {
358 char dirbuf
[PATH_MAX
];
361 if (strcmp(e2
->d_name
, ".") == 0 || strcmp(e2
->d_name
, "..") == 0) continue;
362 snprintf(dirbuf
, sizeof(dirbuf
),
363 "%s/%s", path
, e2
->d_name
);
364 if (!is_directory_get_owner_group(dirbuf
, &owner
, &group
)) continue;
365 if (pthread_setugid_np(owner
, group
) != 0) {
366 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
367 "skipping %s: pthread_setugid_np(%u, %u): %s",
368 dirbuf
, owner
, group
, strerror(errno
));
371 for (i
= 0; args
->dirs
[i
]; i
++) {
372 const char *name
= args
->dirs
[i
];
373 snprintf(dirbuf
, sizeof(dirbuf
),
374 "%s/%s/%s", path
, e2
->d_name
, name
);
375 if (is_directory(dirbuf
)) {
376 // at boot time we clean all files,
377 // otherwise only clean regular files.
378 clean_files_older_than(dirbuf
, when
);
381 if (pthread_setugid_np(KAUTH_UID_NONE
, KAUTH_GID_NONE
) != 0) {
382 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
383 "%s: pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE): %s",
384 dirbuf
, strerror(errno
));
389 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
396 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, strerror(errno
));
402 clean_directories(const char* dirs
[], int machineBoot
) {
403 struct clean_args args
;
408 struct mach_timebase_info info
;
412 mach_timebase_info(&info
);
413 ratio
= (double)info
.numer
/ ((double)info
.denom
* NSEC_PER_SEC
);
414 begin
= mach_absolute_time();
415 if((debug
= fopen("/Debug", "a")) != NULL
) {
416 fprintf(debug
, "clean_directories: machineBoot=%d\n", machineBoot
);
421 args
.machineBoot
= machineBoot
;
422 ret
= pthread_create(&t
, NULL
, clean_thread
, &args
);
424 ret
= pthread_join(t
, NULL
);
426 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "clean_directories: pthread_join: %s",
430 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "clean_directories: pthread_create: %s",
434 end
= mach_absolute_time();
436 fprintf(debug
, "clean_directories: %f secs\n", ratio
* (end
- begin
));
443 main(int argc
, char* argv
[]) {
444 mach_msg_size_t mxmsgsz
= MAX_TRAILER_SIZE
;
446 long idle_timeout
= 30; // default 30 second timeout
452 if((debug
= fopen("/Debug", "a")) != NULL
) {
453 for(i
= 0; i
< argc
; i
++) {
454 fprintf(debug
, " %s", argv
[i
]);
461 // Clean up TemporaryItems directory when launched at boot.
462 // It is safe to clean all file types at this time.
463 if (argc
> 1 && strcmp(argv
[1], "-machineBoot") == 0) {
466 dirs
[i
++] = DIRHELPER_TEMP_STR
;
467 dirs
[i
++] = "TemporaryItems";
468 dirs
[i
++] = "Cleanup At Startup";
470 dirs
[i
++] = DIRHELPER_CACHE_STR
;
473 clean_directories(dirs
, 1);
475 } else if (argc
> 1 && strcmp(argv
[1], "-cleanTemporaryItems") == 0) {
476 const char *dirs
[] = {
481 clean_directories(dirs
, 0);
483 } else if (argc
> 1) {
487 launch_data_t config
= NULL
, checkin
= NULL
;
488 checkin
= launch_data_new_string(LAUNCH_KEY_CHECKIN
);
489 config
= launch_msg(checkin
);
490 if (!config
|| launch_data_get_type(config
) == LAUNCH_DATA_ERRNO
) {
491 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "launchd checkin failed");
496 tmv
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_TIMEOUT
);
498 idle_timeout
= (long)launch_data_get_integer(tmv
);
499 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
500 "idle timeout set: %ld seconds", idle_timeout
);
504 svc
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_MACHSERVICES
);
506 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach services");
510 svc
= launch_data_dict_lookup(svc
, DIRHELPER_BOOTSTRAP_NAME
);
512 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach service: %s",
513 DIRHELPER_BOOTSTRAP_NAME
);
517 mach_port_t mp
= launch_data_get_machport(svc
);
518 if (mp
== MACH_PORT_NULL
) {
519 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "NULL mach service: %s",
520 DIRHELPER_BOOTSTRAP_NAME
);
524 // insert a send right so we can send our idle exit message
525 kr
= mach_port_insert_right(mach_task_self(), mp
, mp
,
526 MACH_MSG_TYPE_MAKE_SEND
);
527 if (kr
!= KERN_SUCCESS
) {
528 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "send right failed: %s",
529 mach_error_string(kr
));
533 // spawn a thread for our idle timeout
535 idle_globals
.mp
= mp
;
536 idle_globals
.timeout
= idle_timeout
;
537 gettimeofday(&idle_globals
.lastmsg
, NULL
);
538 pthread_create(&thread
, NULL
, &idle_thread
, NULL
);
540 // look to see if we have any messages queued. if not, assume
541 // we were launched because of the calendar interval, and attempt
542 // to clean the temporary items.
543 mach_msg_type_number_t status_count
= MACH_PORT_RECEIVE_STATUS_COUNT
;
544 mach_port_status_t status
;
545 kr
= mach_port_get_attributes(mach_task_self(), mp
,
546 MACH_PORT_RECEIVE_STATUS
, (mach_port_info_t
)&status
, &status_count
);
547 if (kr
== KERN_SUCCESS
&& status
.mps_msgcount
== 0) {
548 const char *dirs
[] = {
553 clean_directories(dirs
, 0);
559 kr
= mach_msg_server(dirhelper_server
, mxmsgsz
, mp
,
560 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT
) |
561 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0
));
562 if (kr
!= KERN_SUCCESS
) {
563 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
564 "mach_msg_server(mp): %s", mach_error_string(kr
));