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
)));
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)
83 int is_safeboot(void);
85 void clean_files_older_than(const char* path
, time_t when
);
86 void clean_directories(const char* names
[], int);
89 do___dirhelper_create_user_local(
90 mach_port_t server_port
__attribute__((unused
)),
96 struct passwd
* pwd
= NULL
;
98 gettimeofday(&idle_globals
.lastmsg
, NULL
);
100 audit_token_to_au32(au_tok
,
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
;
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
));
126 // All dirhelper directories are now at the same level, so
127 // we need to remove the DIRHELPER_TOP_STR suffix to get the
129 path
[strlen(path
) - (sizeof(DIRHELPER_TOP_STR
) - 1)] = 0;
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.
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
));
145 *next
++ = '/'; // restore the slash and increment
147 if(next
|| res
) // an error occurred
149 res
= chown(path
, euid
, gid
);
151 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
152 "chown(%s): %s", path
, strerror(errno
));
154 } while(0); // end block
159 do___dirhelper_idle_exit(
160 mach_port_t server_port
__attribute__((unused
)),
161 audit_token_t au_tok
__attribute__((unused
))) {
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
);
176 idle_thread(void* param
__attribute__((unused
))) {
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
);
185 // timeout has elapsed, attempt to idle exit
186 __dirhelper_idle_exit(idle_globals
.mp
);
192 // If when == 0, all files are removed. Otherwise, only regular files that were both created _and_ last modified before `when`.
194 clean_files_older_than(const char* path
, time_t when
) {
197 char* path_argv
[] = { (char*)path
, NULL
};
198 fts
= fts_open(path_argv
, FTS_PHYSICAL
| FTS_XDEV
, NULL
);
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
) {
208 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
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
);
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
);
220 res
= fstat(fd
, &sb
);
221 if ((res
== 0) && (sb
.st_birthtime
< when
) && (sb
.st_atime
< when
)) {
223 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
225 (void)unlink(ent
->fts_path
);
227 (void)flock(fd
, LOCK_UN
);
238 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
240 (void)unlink(ent
->fts_path
);
247 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "rmdir(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
249 (void)rmdir(ent
->fts_path
);
255 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, VAR_FOLDERS_PATH
"%s: %s", ent
->fts_path
, strerror(ent
->fts_errno
));
264 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, VAR_FOLDERS_PATH
"%s: %s", path
, strerror(errno
));
269 file_check(const char* path
, int mode
, int uid
, int gid
, uid_t
* owner
, gid_t
* group
) {
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);
277 if (owner
) *owner
= sb
.st_uid
;
278 if (group
) *group
= sb
.st_gid
;
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
));
293 size_t sbsz
= sizeof(sb
);
295 if (sysctlbyname("kern.safeboot", &sb
, &sbsz
, NULL
, 0) != 0) {
303 clean_thread(void *a
) {
304 struct clean_args
* args
= (struct clean_args
*)a
;
309 if (!args
->machineBoot
) {
312 const char* str
= getenv("CLEAN_FILES_OLDER_THAN_DAYS");
314 days
= strtol(str
, NULL
, 0);
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
);
320 when
= now
.tv_sec
- (days
* 60 * 60 * 24);
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
));
331 if (!is_root_wheel_directory(VAR_FOLDERS_PATH
)) {
332 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, "invalid ownership");
336 if (chroot(VAR_FOLDERS_PATH
)) {
337 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "chroot(%s) failed: %s",
338 VAR_FOLDERS_PATH
, strerror(errno
));
341 if ((d
= opendir("/"))) {
346 while ((e
= readdir(d
))) {
347 if (strcmp(e
->d_name
, ".") == 0 || strcmp(e
->d_name
, "..") == 0) continue;
349 snprintf(path
, sizeof(path
), "%s%s", "/", e
->d_name
);
350 if (is_root_wheel_directory(path
)) {
351 DIR* d2
= opendir(path
);
356 while ((e2
= readdir(d2
))) {
357 char dirbuf
[PATH_MAX
];
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
));
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
);
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
));
388 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
395 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, strerror(errno
));
401 clean_directories(const char* dirs
[], int machineBoot
) {
402 struct clean_args args
;
407 struct mach_timebase_info info
;
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
);
420 args
.machineBoot
= machineBoot
;
421 ret
= pthread_create(&t
, NULL
, clean_thread
, &args
);
423 ret
= pthread_join(t
, NULL
);
425 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "clean_directories: pthread_join: %s",
429 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "clean_directories: pthread_create: %s",
433 end
= mach_absolute_time();
435 fprintf(debug
, "clean_directories: %f secs\n", ratio
* (end
- begin
));
442 main(int argc
, char* argv
[]) {
443 mach_msg_size_t mxmsgsz
= MAX_TRAILER_SIZE
;
445 long idle_timeout
= 30; // default 30 second timeout
451 if((debug
= fopen("/Debug", "a")) != NULL
) {
452 for(i
= 0; i
< argc
; i
++) {
453 fprintf(debug
, " %s", argv
[i
]);
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) {
465 dirs
[i
++] = DIRHELPER_TEMP_STR
;
466 dirs
[i
++] = "TemporaryItems";
467 dirs
[i
++] = "Cleanup At Startup";
469 dirs
[i
++] = DIRHELPER_CACHE_STR
;
472 clean_directories(dirs
, 1);
474 } else if (argc
> 1 && strcmp(argv
[1], "-cleanTemporaryItems") == 0) {
475 const char *dirs
[] = {
480 clean_directories(dirs
, 0);
482 } else if (argc
> 1) {
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");
495 tmv
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_TIMEOUT
);
497 idle_timeout
= launch_data_get_integer(tmv
);
498 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
499 "idle timeout set: %ld seconds", idle_timeout
);
503 svc
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_MACHSERVICES
);
505 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach services");
509 svc
= launch_data_dict_lookup(svc
, DIRHELPER_BOOTSTRAP_NAME
);
511 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach service: %s",
512 DIRHELPER_BOOTSTRAP_NAME
);
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
);
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
));
532 // spawn a thread for our idle timeout
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
);
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
[] = {
552 clean_directories(dirs
, 0);
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
));