2 * Copyright (c) 2006-2007 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@
34 #include <sys/sysctl.h>
37 #include <mach/mach.h>
38 #include <mach/mach_error.h>
39 #include <servers/bootstrap.h>
41 #include <bsm/libbsm.h>
44 #include <membership.h>
46 #include <dirhelper_priv.h>
48 #include "dirhelper_server.h"
50 // globals for idle exit
54 struct timeval lastmsg
;
57 struct idle_globals idle_globals
;
59 void* idle_thread(void* param
__attribute__((unused
)));
61 int 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)
66 int is_safeboot(void);
68 void clean_files_older_than(const char* path
, time_t when
);
69 void clean_directories(const char* names
[], int);
72 do___dirhelper_create_user_local(
73 mach_port_t server_port
__attribute__((unused
)),
79 struct passwd
* pwd
= NULL
;
81 gettimeofday(&idle_globals
.lastmsg
, NULL
);
83 audit_token_to_au32(au_tok
,
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.
97 if (pwd
) gid
= pwd
->pw_gid
;
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
));
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.
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
));
123 *next
++ = '/'; // restore the slash and increment
125 if(next
|| res
) // an error occurred
127 res
= chown(path
, euid
, gid
);
129 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
130 "chown(%s): %s", path
, strerror(errno
));
132 } while(0); // end block
137 do___dirhelper_idle_exit(
138 mach_port_t server_port
__attribute__((unused
)),
139 audit_token_t au_tok
__attribute__((unused
))) {
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
);
154 idle_thread(void* param
__attribute__((unused
))) {
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
);
163 // timeout has elapsed, attempt to idle exit
164 __dirhelper_idle_exit(idle_globals
.mp
);
170 // If when == 0, all files are removed. Otherwise, only regular files older than when.
172 clean_files_older_than(const char* path
, time_t when
) {
175 char* path_argv
[] = { (char*)path
, NULL
};
176 fts
= fts_open(path_argv
, FTS_PHYSICAL
| FTS_XDEV
, NULL
);
179 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning " VAR_FOLDERS_PATH
"%s", path
);
180 while ((ent
= fts_read(fts
))) {
181 switch(ent
->fts_info
) {
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.
190 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
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
);
196 int res
= flock(fd
, LOCK_EX
| LOCK_NB
);
199 res
= fstat(fd
, &sb
);
200 if (res
== 0 && sb
.st_atime
< when
) {
202 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
204 (void)unlink(ent
->fts_path
);
206 (void)flock(fd
, LOCK_UN
);
217 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "unlink(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
219 (void)unlink(ent
->fts_path
);
226 asl_log(NULL
, NULL
, ASL_LEVEL_ALERT
, "rmdir(" VAR_FOLDERS_PATH
"%s)", ent
->fts_path
);
228 (void)rmdir(ent
->fts_path
);
234 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, VAR_FOLDERS_PATH
"%s: %s", ent
->fts_path
, strerror(ent
->fts_errno
));
243 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, VAR_FOLDERS_PATH
"%s: %s", path
, strerror(errno
));
248 file_check(const char* path
, int mode
, int uid
, int gid
) {
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);
256 if (errno
!= ENOENT
) {
257 /* This will print a shorter path after chroot() */
258 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
268 size_t sbsz
= sizeof(sb
);
270 if (sysctlbyname("kern.safeboot", &sb
, &sbsz
, NULL
, 0) != 0) {
278 clean_directories(const char* dirs
[], int machineBoot
) {
286 const char* str
= getenv("CLEAN_FILES_OLDER_THAN_DAYS");
288 days
= strtol(str
, NULL
, 0);
290 (void)gettimeofday(&now
, NULL
);
291 for (i
= 0; dirs
[i
]; i
++)
292 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning %s older than %ld days", dirs
[i
], days
);
294 when
= now
.tv_sec
- (days
* 60 * 60 * 24);
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
));
305 if (!is_root_wheel_directory(VAR_FOLDERS_PATH
)) {
306 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, "invalid ownership");
310 if (chroot(VAR_FOLDERS_PATH
)) {
311 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "chroot(%s) failed: %s",
312 VAR_FOLDERS_PATH
, strerror(errno
));
314 if ((d
= opendir("/"))) {
319 while ((e
= readdir(d
))) {
320 if (strcmp(e
->d_name
, ".") == 0 || strcmp(e
->d_name
, "..") == 0) continue;
322 snprintf(path
, sizeof(path
), "%s%s", "/", e
->d_name
);
323 if (is_root_wheel_directory(path
)) {
324 DIR* d2
= opendir(path
);
329 while ((e2
= readdir(d2
))) {
330 char temporary_items
[PATH_MAX
];
331 if (strcmp(e2
->d_name
, ".") == 0 || strcmp(e2
->d_name
, "..") == 0) continue;
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
);
345 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
352 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, strerror(errno
));
357 main(int argc
, char* argv
[]) {
358 mach_msg_size_t mxmsgsz
= MAX_TRAILER_SIZE
;
360 long idle_timeout
= 30; // default 30 second timeout
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) {
367 dirs
[i
++] = DIRHELPER_TEMP_STR
;
368 dirs
[i
++] = "TemporaryItems";
369 dirs
[i
++] = "Cleanup At Startup";
371 dirs
[i
++] = DIRHELPER_CACHE_STR
;
374 clean_directories(dirs
, 1);
376 } else if (argc
> 1 && strcmp(argv
[1], "-cleanTemporaryItems") == 0) {
377 const char *dirs
[] = {
382 clean_directories(dirs
, 0);
384 } else if (argc
> 1) {
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");
397 tmv
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_TIMEOUT
);
399 idle_timeout
= launch_data_get_integer(tmv
);
400 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
401 "idle timeout set: %ld seconds", idle_timeout
);
405 svc
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_MACHSERVICES
);
407 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach services");
411 svc
= launch_data_dict_lookup(svc
, DIRHELPER_BOOTSTRAP_NAME
);
413 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach service: %s",
414 DIRHELPER_BOOTSTRAP_NAME
);
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
);
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
));
434 // spawn a thread for our idle timeout
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
);
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) {
449 const char *dirs
[] = {
454 clean_directories(dirs
, 0);
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
));