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_directory(const char* name
, 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 %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.
189 (void)unlink(ent
->fts_path
);
190 } else if (S_ISREG(ent
->fts_statp
->st_mode
) && ent
->fts_statp
->st_atime
< when
) {
191 int fd
= open(ent
->fts_path
, O_RDONLY
| O_NONBLOCK
);
193 int res
= flock(fd
, LOCK_EX
| LOCK_NB
);
196 res
= fstat(fd
, &sb
);
197 if (res
== 0 && sb
.st_atime
< when
) {
198 (void)unlink(ent
->fts_path
);
200 (void)flock(fd
, LOCK_UN
);
210 (void)unlink(ent
->fts_path
);
216 (void)rmdir(ent
->fts_path
);
222 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", ent
->fts_path
, strerror(ent
->fts_errno
));
231 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
236 file_check(const char* path
, int mode
, int uid
, int gid
) {
239 if (lstat(path
, &sb
) == 0) {
240 check
= check
&& ((sb
.st_mode
& S_IFMT
) == mode
);
241 check
= check
&& ((sb
.st_uid
== (uid_t
)uid
) || uid
== -1);
242 check
= check
&& ((sb
.st_gid
== (gid_t
)gid
) || gid
== -1);
244 if (errno
!= ENOENT
) {
245 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
255 size_t sbsz
= sizeof(sb
);
257 if (sysctlbyname("kern.safeboot", &sb
, &sbsz
, NULL
, 0) != 0) {
265 clean_directory(const char* name
, int machineBoot
) {
272 const char* str
= getenv("CLEAN_FILES_OLDER_THAN_DAYS");
274 days
= strtol(str
, NULL
, 0);
276 (void)gettimeofday(&now
, NULL
);
278 asl_log(NULL
, NULL
, ASL_LEVEL_INFO
, "Cleaning %s older than %ld days", name
, days
);
280 when
= now
.tv_sec
- (days
* 60 * 60 * 24);
283 // Look up the boot time
284 struct timespec boottime
;
285 size_t len
= sizeof(boottime
);
286 if (sysctlbyname("kern.boottime", &boottime
, &len
, NULL
, 0) == -1) {
287 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", "sysctl kern.boottime", strerror(errno
));
291 if (!is_root_wheel_directory(VAR_FOLDERS_PATH
)) {
292 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, "invalid ownership");
296 if ((d
= opendir(VAR_FOLDERS_PATH
))) {
301 while ((e
= readdir(d
))) {
302 if (strcmp(e
->d_name
, ".") == 0 || strcmp(e
->d_name
, "..") == 0) continue;
304 snprintf(path
, sizeof(path
), "%s%s", VAR_FOLDERS_PATH
, e
->d_name
);
305 if (is_root_wheel_directory(path
)) {
306 DIR* d2
= opendir(path
);
311 while ((e2
= readdir(d2
))) {
312 char temporary_items
[PATH_MAX
];
313 if (strcmp(e2
->d_name
, ".") == 0 || strcmp(e2
->d_name
, "..") == 0) continue;
315 snprintf(temporary_items
, sizeof(temporary_items
),
316 "%s/%s/%s", path
, e2
->d_name
, name
);
317 if (is_directory(temporary_items
)) {
318 // at boot time we clean all files,
319 // otherwise only clean regular files.
320 clean_files_older_than(temporary_items
, when
);
326 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", path
, strerror(errno
));
333 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "%s: %s", VAR_FOLDERS_PATH
, strerror(errno
));
338 main(int argc
, char* argv
[]) {
339 mach_msg_size_t mxmsgsz
= MAX_TRAILER_SIZE
;
341 long idle_timeout
= 30; // default 30 second timeout
343 // Clean up TemporaryItems directory when launched at boot.
344 // It is safe to clean all file types at this time.
345 if (argc
> 1 && strcmp(argv
[1], "-machineBoot") == 0) {
346 clean_directory(DIRHELPER_TEMP_STR
, 1);
347 clean_directory("TemporaryItems", 1);
348 clean_directory("Cleanup At Startup", 1);
349 if (is_safeboot()) clean_directory(DIRHELPER_CACHE_STR
, 1);
351 } else if (argc
> 1 && strcmp(argv
[1], "-cleanTemporaryItems") == 0) {
352 clean_directory(DIRHELPER_TEMP_STR
, 0);
353 clean_directory("TemporaryItems", 0);
355 } else if (argc
> 1) {
359 launch_data_t config
= NULL
, checkin
= NULL
;
360 checkin
= launch_data_new_string(LAUNCH_KEY_CHECKIN
);
361 config
= launch_msg(checkin
);
362 if (!config
|| launch_data_get_type(config
) == LAUNCH_DATA_ERRNO
) {
363 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "launchd checkin failed");
368 tmv
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_TIMEOUT
);
370 idle_timeout
= launch_data_get_integer(tmv
);
371 asl_log(NULL
, NULL
, ASL_LEVEL_DEBUG
,
372 "idle timeout set: %ld seconds", idle_timeout
);
376 svc
= launch_data_dict_lookup(config
, LAUNCH_JOBKEY_MACHSERVICES
);
378 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach services");
382 svc
= launch_data_dict_lookup(svc
, DIRHELPER_BOOTSTRAP_NAME
);
384 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "no mach service: %s",
385 DIRHELPER_BOOTSTRAP_NAME
);
389 mach_port_t mp
= launch_data_get_machport(svc
);
390 if (mp
== MACH_PORT_NULL
) {
391 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "NULL mach service: %s",
392 DIRHELPER_BOOTSTRAP_NAME
);
396 // insert a send right so we can send our idle exit message
397 kr
= mach_port_insert_right(mach_task_self(), mp
, mp
,
398 MACH_MSG_TYPE_MAKE_SEND
);
399 if (kr
!= KERN_SUCCESS
) {
400 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
, "send right failed: %s",
401 mach_error_string(kr
));
405 // spawn a thread for our idle timeout
407 idle_globals
.mp
= mp
;
408 idle_globals
.timeout
= idle_timeout
;
409 gettimeofday(&idle_globals
.lastmsg
, NULL
);
410 pthread_create(&thread
, NULL
, &idle_thread
, NULL
);
412 // look to see if we have any messages queued. if not, assume
413 // we were launched because of the calendar interval, and attempt
414 // to clean the temporary items.
415 mach_msg_type_number_t status_count
= MACH_PORT_RECEIVE_STATUS_COUNT
;
416 mach_port_status_t status
;
417 kr
= mach_port_get_attributes(mach_task_self(), mp
,
418 MACH_PORT_RECEIVE_STATUS
, (mach_port_info_t
)&status
, &status_count
);
419 if (kr
== KERN_SUCCESS
&& status
.mps_msgcount
== 0) {
420 clean_directory(DIRHELPER_TEMP_STR
, 0);
421 clean_directory("TemporaryItems", 0);
426 kr
= mach_msg_server(dirhelper_server
, mxmsgsz
, mp
,
427 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT
) |
428 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0
));
429 if (kr
!= KERN_SUCCESS
) {
430 asl_log(NULL
, NULL
, ASL_LEVEL_ERR
,
431 "mach_msg_server(mp): %s", mach_error_string(kr
));