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.h"
49 #include "dirhelperServer.h"
51 // globals for idle exit
55 struct timeval lastmsg
;
58 struct idle_globals idle_globals
;
60 void* idle_thread(void* param
__attribute__((unused
)));
62 int file_check(const char* path
, int mode
, int uid
, int gid
);
63 #define is_file(x) file_check((x), S_IFREG, -1, -1)
64 #define is_directory(x) file_check((x), S_IFDIR, -1, -1)
65 #define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0)
67 int is_safeboot(void);
69 void clean_files_older_than(const char* path
, time_t when
);
70 void clean_directory(const char* name
, int);
73 do___dirhelper_create_user_local(
74 mach_port_t server_port
__attribute__((unused
)),
80 struct passwd
* pwd
= NULL
;
82 gettimeofday(&idle_globals
.lastmsg
, NULL
);
84 audit_token_to_au32(au_tok
,
94 // Look-up the primary gid of the user. We'll use this for chown(2)
95 // so that the created directory is owned by a group that the user
96 // belongs to, avoiding warnings if files are moved outside this dir.
98 if (pwd
) gid
= pwd
->pw_gid
;
104 if (__user_local_dirname(euid
, DIRHELPER_USER_LOCAL
, path
, sizeof(path
)) == NULL
) {
105 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
106 "__user_local_dirname: %s", strerror(errno
));
111 // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory
112 // in path, ignoring failure if it already exists.
113 // 2. Change ownership of directory to the user.
115 next
= path
+ strlen(VAR_FOLDERS_PATH
);
116 while ((next
= strchr(next
, '/')) != NULL
) {
117 *next
= 0; // temporarily truncate
118 res
= mkdir(path
, 0755);
119 if (res
!= 0 && errno
!= EEXIST
) {
120 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
121 "mkdir(%s): %s", path
, strerror(errno
));
124 *next
++ = '/'; // restore the slash and increment
126 if(next
|| res
) // an error occurred
128 res
= chown(path
, euid
, gid
);
130 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
131 "chown(%s): %s", path
, strerror(errno
));
133 } while(0); // end block
138 do___dirhelper_idle_exit(
139 mach_port_t server_port
__attribute__((unused
)),
140 audit_token_t au_tok
__attribute__((unused
))) {
143 gettimeofday(&now
, NULL
);
144 long delta
= now
.tv_sec
- idle_globals
.lastmsg
.tv_sec
;
145 if (delta
>= idle_globals
.timeout
) {
146 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
147 "idle exit after %ld seconds", delta
);
155 idle_thread(void* param
__attribute__((unused
))) {
158 gettimeofday(&now
, NULL
);
159 long delta
= (now
.tv_sec
- idle_globals
.lastmsg
.tv_sec
);
160 if (delta
< idle_globals
.timeout
) {
161 // sleep for remainder of timeout
162 sleep(idle_globals
.timeout
- delta
);
164 // timeout has elapsed, attempt to idle exit
165 __dirhelper_idle_exit(idle_globals
.mp
);
171 // If when == 0, all files are removed. Otherwise, only regular files older than when.
173 clean_files_older_than(const char* path
, time_t when
) {
176 char* path_argv
[] = { (char*)path
, NULL
};
177 fts
= fts_open(path_argv
, FTS_PHYSICAL
| FTS_XDEV
, NULL
);
180 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning %s", path
);
181 while ((ent
= fts_read(fts
))) {
182 switch(ent
->fts_info
) {
185 // Unlink the file if it has not been accessed since
186 // the specified time. Obtain an exclusive lock so
187 // that we can avoid a race with other processes
188 // attempting to open the file.
190 (void)unlink(ent
->fts_path
);
191 } else if (S_ISREG(ent
->fts_statp
->st_mode
) && ent
->fts_statp
->st_atime
< when
) {
192 int fd
= open(ent
->fts_path
, O_RDONLY
| O_NONBLOCK
);
194 int res
= flock(fd
, LOCK_EX
| LOCK_NB
);
197 res
= fstat(fd
, &sb
);
198 if (res
== 0 && sb
.st_atime
< when
) {
199 (void)unlink(ent
->fts_path
);
201 (void)flock(fd
, LOCK_UN
);
211 (void)unlink(ent
->fts_path
);
217 (void)rmdir(ent
->fts_path
);
223 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", ent
->fts_path
, strerror(ent
->fts_errno
));
232 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
237 file_check(const char* path
, int mode
, int uid
, int gid
) {
240 if (lstat(path
, &sb
) == 0) {
241 check
= check
&& ((sb
.st_mode
& S_IFMT
) == mode
);
242 check
= check
&& ((sb
.st_uid
== (uid_t
)uid
) || uid
== -1);
243 check
= check
&& ((sb
.st_gid
== (gid_t
)gid
) || gid
== -1);
245 if (errno
!= ENOENT
) {
246 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
256 size_t sbsz
= sizeof(sb
);
258 if (sysctlbyname("kern.safeboot", &sb
, &sbsz
, NULL
, 0) != 0) {
266 clean_directory(const char* name
, int machineBoot
) {
273 const char* str
= getenv("CLEAN_FILES_OLDER_THAN_DAYS");
275 days
= strtol(str
, NULL
, 0);
277 (void)gettimeofday(&now
, NULL
);
279 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning %s older than %ld days", name
, days
);
281 when
= now
.tv_sec
- (days
* 60 * 60 * 24);
284 // Look up the boot time
285 struct timespec boottime
;
286 size_t len
= sizeof(boottime
);
287 if (sysctlbyname("kern.boottime", &boottime
, &len
, NULL
, 0) == -1) {
288 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", "sysctl kern.boottime", strerror(errno
));
292 if (!is_root_wheel_directory(VAR_FOLDERS_PATH
)) {
293 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, "invalid ownership");
297 if ((d
= opendir(VAR_FOLDERS_PATH
))) {
302 while ((e
= readdir(d
))) {
303 if (strcmp(e
->d_name
, ".") == 0 || strcmp(e
->d_name
, "..") == 0) continue;
305 snprintf(path
, sizeof(path
), "%s%s", VAR_FOLDERS_PATH
, e
->d_name
);
306 if (is_root_wheel_directory(path
)) {
307 DIR* d2
= opendir(path
);
312 while ((e2
= readdir(d2
))) {
313 char temporary_items
[PATH_MAX
];
314 if (strcmp(e2
->d_name
, ".") == 0 || strcmp(e2
->d_name
, "..") == 0) continue;
316 snprintf(temporary_items
, sizeof(temporary_items
),
317 "%s/%s/%s", path
, e2
->d_name
, name
);
318 if (is_directory(temporary_items
)) {
319 // at boot time we clean all files,
320 // otherwise only clean regular files.
321 clean_files_older_than(temporary_items
, when
);
327 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
334 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, strerror(errno
));
339 main(int argc
, char* argv
[]) {
340 mach_msg_size_t mxmsgsz
= MAX_TRAILER_SIZE
;
342 long idle_timeout
= 30; // default 30 second timeout
344 // Clean up TemporaryItems directory when launched at boot.
345 // It is safe to clean all file types at this time.
346 if (argc
> 1 && strcmp(argv
[1], "-machineBoot") == 0) {
347 clean_directory(DIRHELPER_TEMP_STR
, 1);
348 clean_directory("TemporaryItems", 1);
349 clean_directory("Cleanup At Startup", 1);
350 if (is_safeboot()) clean_directory(DIRHELPER_CACHE_STR
, 1);
352 } else if (argc
> 1 && strcmp(argv
[1], "-cleanTemporaryItems") == 0) {
353 clean_directory(DIRHELPER_TEMP_STR
, 0);
354 clean_directory("TemporaryItems", 0);
356 } else if (argc
> 1) {
360 launch_data_t config
= NULL
, checkin
= NULL
;
361 checkin
= launch_data_new_string(LAUNCH_KEY_CHECKIN
);
362 config
= launch_msg(checkin
);
363 if (!config
|| launch_data_get_type(config
) == LAUNCH_DATA_ERRNO
) {
364 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "launchd checkin failed");
369 tmv
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_TIMEOUT
);
371 idle_timeout
= launch_data_get_integer(tmv
);
372 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
373 "idle timeout set: %ld seconds", idle_timeout
);
377 svc
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_MACHSERVICES
);
379 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach services");
383 svc
= launch_data_dict_lookup(svc
, DIRHELPER_BOOTSTRAP_NAME
);
385 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach service: %s",
386 DIRHELPER_BOOTSTRAP_NAME
);
390 mach_port_t mp
= launch_data_get_machport(svc
);
391 if (mp
== MACH_PORT_NULL
) {
392 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "NULL mach service: %s",
393 DIRHELPER_BOOTSTRAP_NAME
);
397 // insert a send right so we can send our idle exit message
398 kr
= mach_port_insert_right(mach_task_self(), mp
, mp
,
399 MACH_MSG_TYPE_MAKE_SEND
);
400 if (kr
!= KERN_SUCCESS
) {
401 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "send right failed: %s",
402 mach_error_string(kr
));
406 // spawn a thread for our idle timeout
408 idle_globals
.mp
= mp
;
409 idle_globals
.timeout
= idle_timeout
;
410 gettimeofday(&idle_globals
.lastmsg
, NULL
);
411 pthread_create(&thread
, NULL
, &idle_thread
, NULL
);
413 // look to see if we have any messages queued. if not, assume
414 // we were launched because of the calendar interval, and attempt
415 // to clean the temporary items.
416 mach_msg_type_number_t status_count
= MACH_PORT_RECEIVE_STATUS_COUNT
;
417 mach_port_status_t status
;
418 kr
= mach_port_get_attributes(mach_task_self(), mp
,
419 MACH_PORT_RECEIVE_STATUS
, (mach_port_info_t
)&status
, &status_count
);
420 if (kr
== KERN_SUCCESS
&& status
.mps_msgcount
== 0) {
421 clean_directory(DIRHELPER_TEMP_STR
, 0);
422 clean_directory("TemporaryItems", 0);
427 kr
= mach_msg_server(dirhelper_server
, mxmsgsz
, mp
,
428 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT
) |
429 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0
));
430 if (kr
!= KERN_SUCCESS
) {
431 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
432 "mach_msg_server(mp): %s", mach_error_string(kr
));