10f66f451Sopenharmony_ci/* crontab.c - files used to schedule the execution of programs. 20f66f451Sopenharmony_ci * 30f66f451Sopenharmony_ci * Copyright 2014 Ranjan Kumar <ranjankumar.bth@gmail.com> 40f66f451Sopenharmony_ci * 50f66f451Sopenharmony_ci * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html 60f66f451Sopenharmony_ci 70f66f451Sopenharmony_ciUSE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) 80f66f451Sopenharmony_ci 90f66f451Sopenharmony_ciconfig CRONTAB 100f66f451Sopenharmony_ci bool "crontab" 110f66f451Sopenharmony_ci default n 120f66f451Sopenharmony_ci depends on TOYBOX_FORK 130f66f451Sopenharmony_ci help 140f66f451Sopenharmony_ci usage: crontab [-u user] FILE 150f66f451Sopenharmony_ci [-u user] [-e | -l | -r] 160f66f451Sopenharmony_ci [-c dir] 170f66f451Sopenharmony_ci 180f66f451Sopenharmony_ci Files used to schedule the execution of programs. 190f66f451Sopenharmony_ci 200f66f451Sopenharmony_ci -c crontab dir 210f66f451Sopenharmony_ci -e edit user's crontab 220f66f451Sopenharmony_ci -l list user's crontab 230f66f451Sopenharmony_ci -r delete user's crontab 240f66f451Sopenharmony_ci -u user 250f66f451Sopenharmony_ci FILE Replace crontab by FILE ('-': stdin) 260f66f451Sopenharmony_ci*/ 270f66f451Sopenharmony_ci#define FOR_crontab 280f66f451Sopenharmony_ci#include "toys.h" 290f66f451Sopenharmony_ci 300f66f451Sopenharmony_ciGLOBALS( 310f66f451Sopenharmony_ci char *user; 320f66f451Sopenharmony_ci char *cdir; 330f66f451Sopenharmony_ci) 340f66f451Sopenharmony_ci 350f66f451Sopenharmony_cistatic char *omitspace(char *line) 360f66f451Sopenharmony_ci{ 370f66f451Sopenharmony_ci while (*line == ' ' || *line == '\t') line++; 380f66f451Sopenharmony_ci return line; 390f66f451Sopenharmony_ci} 400f66f451Sopenharmony_ci 410f66f451Sopenharmony_ci/* 420f66f451Sopenharmony_ci * Names can also be used for the 'month' and 'day of week' fields 430f66f451Sopenharmony_ci * (First three letters of the particular day or month). 440f66f451Sopenharmony_ci */ 450f66f451Sopenharmony_cistatic int getindex(char *src, int size) 460f66f451Sopenharmony_ci{ 470f66f451Sopenharmony_ci int i; 480f66f451Sopenharmony_ci char days[]={"sun""mon""tue""wed""thu""fri""sat"}; 490f66f451Sopenharmony_ci char months[]={"jan""feb""mar""apr""may""jun""jul" 500f66f451Sopenharmony_ci "aug""sep""oct""nov""dec"}; 510f66f451Sopenharmony_ci char *field = (size == 12) ? months : days; 520f66f451Sopenharmony_ci 530f66f451Sopenharmony_ci // strings are not allowed for min, hour and dom fields. 540f66f451Sopenharmony_ci if (!(size == 7 || size == 12)) return -1; 550f66f451Sopenharmony_ci 560f66f451Sopenharmony_ci for (i = 0; field[i]; i += 3) { 570f66f451Sopenharmony_ci if (!strncasecmp(src, &field[i], 3)) 580f66f451Sopenharmony_ci return (i/3); 590f66f451Sopenharmony_ci } 600f66f451Sopenharmony_ci return -1; 610f66f451Sopenharmony_ci} 620f66f451Sopenharmony_ci 630f66f451Sopenharmony_cistatic long getval(char *num, long low, long high) 640f66f451Sopenharmony_ci{ 650f66f451Sopenharmony_ci long val = strtol(num, &num, 10); 660f66f451Sopenharmony_ci 670f66f451Sopenharmony_ci if (*num || (val < low) || (val > high)) return -1; 680f66f451Sopenharmony_ci return val; 690f66f451Sopenharmony_ci} 700f66f451Sopenharmony_ci 710f66f451Sopenharmony_ci// Validate minute, hour, day of month, month and day of week fields. 720f66f451Sopenharmony_cistatic int validate_component(int min, int max, char *src) 730f66f451Sopenharmony_ci{ 740f66f451Sopenharmony_ci int skip = 0; 750f66f451Sopenharmony_ci char *ptr; 760f66f451Sopenharmony_ci 770f66f451Sopenharmony_ci if (!src) return 1; 780f66f451Sopenharmony_ci if ((ptr = strchr(src, '/'))) { 790f66f451Sopenharmony_ci *ptr++ = 0; 800f66f451Sopenharmony_ci if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) return 1; 810f66f451Sopenharmony_ci } 820f66f451Sopenharmony_ci 830f66f451Sopenharmony_ci if (*src == '-' || *src == ',') return 1; 840f66f451Sopenharmony_ci if (*src == '*') { 850f66f451Sopenharmony_ci if (*(src+1)) return 1; 860f66f451Sopenharmony_ci } 870f66f451Sopenharmony_ci else { 880f66f451Sopenharmony_ci for (;;) { 890f66f451Sopenharmony_ci char *ctoken = strsep(&src, ","), *dtoken; 900f66f451Sopenharmony_ci 910f66f451Sopenharmony_ci if (!ctoken) break; 920f66f451Sopenharmony_ci if (!*ctoken) return 1; 930f66f451Sopenharmony_ci 940f66f451Sopenharmony_ci // validate start position. 950f66f451Sopenharmony_ci dtoken = strsep(&ctoken, "-"); 960f66f451Sopenharmony_ci if (isdigit(*dtoken)) { 970f66f451Sopenharmony_ci if (getval(dtoken, min, (min ? max : max-1)) < 0) return 1; 980f66f451Sopenharmony_ci } else if (getindex(dtoken, max) < 0) return 1; 990f66f451Sopenharmony_ci 1000f66f451Sopenharmony_ci // validate end position. 1010f66f451Sopenharmony_ci if (!ctoken) { 1020f66f451Sopenharmony_ci if (skip) return 1; // case 10/20 or 1,2,4/3 1030f66f451Sopenharmony_ci } 1040f66f451Sopenharmony_ci else if (*ctoken) {// e.g. N-M 1050f66f451Sopenharmony_ci if (isdigit(*ctoken)) { 1060f66f451Sopenharmony_ci if (getval(ctoken, min, (min ? max : max-1)) < 0) return 1; 1070f66f451Sopenharmony_ci } else if (getindex(ctoken, max) < 0) return 1; 1080f66f451Sopenharmony_ci } else return 1; // error condition 'N-' 1090f66f451Sopenharmony_ci } 1100f66f451Sopenharmony_ci } 1110f66f451Sopenharmony_ci return 0; 1120f66f451Sopenharmony_ci} 1130f66f451Sopenharmony_ci 1140f66f451Sopenharmony_cistatic int parse_crontab(char *fname) 1150f66f451Sopenharmony_ci{ 1160f66f451Sopenharmony_ci FILE *fp = xfopen(fname, "r"); 1170f66f451Sopenharmony_ci long len = 0; 1180f66f451Sopenharmony_ci char *line = NULL; 1190f66f451Sopenharmony_ci size_t allocated_length; 1200f66f451Sopenharmony_ci int lno; 1210f66f451Sopenharmony_ci 1220f66f451Sopenharmony_ci for (lno = 1; (len = getline(&line, &allocated_length, fp)) > 0; lno++) { 1230f66f451Sopenharmony_ci char *name, *val, *tokens[5] = {0,}, *ptr = line; 1240f66f451Sopenharmony_ci int count = 0; 1250f66f451Sopenharmony_ci 1260f66f451Sopenharmony_ci if (line[len - 1] == '\n') line[--len] = '\0'; 1270f66f451Sopenharmony_ci else { 1280f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno); 1290f66f451Sopenharmony_ci goto OUT; 1300f66f451Sopenharmony_ci } 1310f66f451Sopenharmony_ci 1320f66f451Sopenharmony_ci ptr = omitspace(ptr); 1330f66f451Sopenharmony_ci if (!*ptr || *ptr == '#' || *ptr == '@') continue; 1340f66f451Sopenharmony_ci while (count<5) { 1350f66f451Sopenharmony_ci int len = strcspn(ptr, " \t"); 1360f66f451Sopenharmony_ci 1370f66f451Sopenharmony_ci if (ptr[len]) ptr[len++] = '\0'; 1380f66f451Sopenharmony_ci tokens[count++] = ptr; 1390f66f451Sopenharmony_ci ptr += len; 1400f66f451Sopenharmony_ci ptr = omitspace(ptr); 1410f66f451Sopenharmony_ci if (!*ptr) break; 1420f66f451Sopenharmony_ci } 1430f66f451Sopenharmony_ci switch (count) { 1440f66f451Sopenharmony_ci case 1: // form SHELL=/bin/sh 1450f66f451Sopenharmony_ci name = tokens[0]; 1460f66f451Sopenharmony_ci if ((val = strchr(name, '='))) *val++ = 0; 1470f66f451Sopenharmony_ci if (!val || !*val) { 1480f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 1490f66f451Sopenharmony_ci goto OUT; 1500f66f451Sopenharmony_ci } 1510f66f451Sopenharmony_ci break; 1520f66f451Sopenharmony_ci case 2: // form SHELL =/bin/sh or SHELL= /bin/sh 1530f66f451Sopenharmony_ci name = tokens[0]; 1540f66f451Sopenharmony_ci if ((val = strchr(name, '='))) { 1550f66f451Sopenharmony_ci *val = 0; 1560f66f451Sopenharmony_ci val = tokens[1]; 1570f66f451Sopenharmony_ci } else { 1580f66f451Sopenharmony_ci if (*(tokens[1]) != '=') { 1590f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 1600f66f451Sopenharmony_ci goto OUT; 1610f66f451Sopenharmony_ci } 1620f66f451Sopenharmony_ci val = tokens[1] + 1; 1630f66f451Sopenharmony_ci } 1640f66f451Sopenharmony_ci if (!*val) { 1650f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 1660f66f451Sopenharmony_ci goto OUT; 1670f66f451Sopenharmony_ci } 1680f66f451Sopenharmony_ci break; 1690f66f451Sopenharmony_ci case 3: // NAME = VAL 1700f66f451Sopenharmony_ci name = tokens[0]; 1710f66f451Sopenharmony_ci val = tokens[2]; 1720f66f451Sopenharmony_ci if (*(tokens[1]) != '=') { 1730f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); 1740f66f451Sopenharmony_ci goto OUT; 1750f66f451Sopenharmony_ci } 1760f66f451Sopenharmony_ci break; 1770f66f451Sopenharmony_ci default: 1780f66f451Sopenharmony_ci if (validate_component(0, 60, tokens[0])) { 1790f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno); 1800f66f451Sopenharmony_ci goto OUT; 1810f66f451Sopenharmony_ci } 1820f66f451Sopenharmony_ci if (validate_component(0, 24, tokens[1])) { 1830f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno); 1840f66f451Sopenharmony_ci goto OUT; 1850f66f451Sopenharmony_ci } 1860f66f451Sopenharmony_ci if (validate_component(1, 31, tokens[2])) { 1870f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno); 1880f66f451Sopenharmony_ci goto OUT; 1890f66f451Sopenharmony_ci } 1900f66f451Sopenharmony_ci if (validate_component(1, 12, tokens[3])) { 1910f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno); 1920f66f451Sopenharmony_ci goto OUT; 1930f66f451Sopenharmony_ci } 1940f66f451Sopenharmony_ci if (validate_component(0, 7, tokens[4])) { 1950f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno); 1960f66f451Sopenharmony_ci goto OUT; 1970f66f451Sopenharmony_ci } 1980f66f451Sopenharmony_ci if (!*ptr) { // don't have any cmd to execute. 1990f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno); 2000f66f451Sopenharmony_ci goto OUT; 2010f66f451Sopenharmony_ci } 2020f66f451Sopenharmony_ci break; 2030f66f451Sopenharmony_ci } 2040f66f451Sopenharmony_ci } 2050f66f451Sopenharmony_ci free(line); 2060f66f451Sopenharmony_ci fclose(fp); 2070f66f451Sopenharmony_ci return 0; 2080f66f451Sopenharmony_ciOUT: 2090f66f451Sopenharmony_ci free(line); 2100f66f451Sopenharmony_ci printf("Error at line no %s", toybuf); 2110f66f451Sopenharmony_ci fclose(fp); 2120f66f451Sopenharmony_ci return 1; 2130f66f451Sopenharmony_ci} 2140f66f451Sopenharmony_ci 2150f66f451Sopenharmony_cistatic void do_list(char *name) 2160f66f451Sopenharmony_ci{ 2170f66f451Sopenharmony_ci int fdin; 2180f66f451Sopenharmony_ci 2190f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name); 2200f66f451Sopenharmony_ci fdin = xopenro(toybuf); 2210f66f451Sopenharmony_ci xsendfile(fdin, 1); 2220f66f451Sopenharmony_ci xclose(fdin); 2230f66f451Sopenharmony_ci} 2240f66f451Sopenharmony_ci 2250f66f451Sopenharmony_cistatic void do_remove(char *name) 2260f66f451Sopenharmony_ci{ 2270f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name); 2280f66f451Sopenharmony_ci if (unlink(toybuf)) 2290f66f451Sopenharmony_ci error_exit("No crontab for '%s'", name); 2300f66f451Sopenharmony_ci} 2310f66f451Sopenharmony_ci 2320f66f451Sopenharmony_cistatic void update_crontab(char *src, char *dest) 2330f66f451Sopenharmony_ci{ 2340f66f451Sopenharmony_ci int fdin, fdout; 2350f66f451Sopenharmony_ci 2360f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, dest); 2370f66f451Sopenharmony_ci fdout = xcreate(toybuf, O_WRONLY|O_CREAT|O_TRUNC, 0600); 2380f66f451Sopenharmony_ci fdin = xopenro(src); 2390f66f451Sopenharmony_ci xsendfile(fdin, fdout); 2400f66f451Sopenharmony_ci xclose(fdin); 2410f66f451Sopenharmony_ci 2420f66f451Sopenharmony_ci fchown(fdout, getuid(), geteuid()); 2430f66f451Sopenharmony_ci xclose(fdout); 2440f66f451Sopenharmony_ci} 2450f66f451Sopenharmony_ci 2460f66f451Sopenharmony_cistatic void do_replace(char *name) 2470f66f451Sopenharmony_ci{ 2480f66f451Sopenharmony_ci char *fname = *toys.optargs ? *toys.optargs : "-"; 2490f66f451Sopenharmony_ci char tname[] = "/tmp/crontab.XXXXXX"; 2500f66f451Sopenharmony_ci 2510f66f451Sopenharmony_ci if ((*fname == '-') && !*(fname+1)) { 2520f66f451Sopenharmony_ci int tfd = mkstemp(tname); 2530f66f451Sopenharmony_ci 2540f66f451Sopenharmony_ci if (tfd < 0) perror_exit("mkstemp"); 2550f66f451Sopenharmony_ci xsendfile(0, tfd); 2560f66f451Sopenharmony_ci xclose(tfd); 2570f66f451Sopenharmony_ci fname = tname; 2580f66f451Sopenharmony_ci } 2590f66f451Sopenharmony_ci 2600f66f451Sopenharmony_ci if (parse_crontab(fname)) 2610f66f451Sopenharmony_ci error_exit("errors in crontab file '%s', can't install.", fname); 2620f66f451Sopenharmony_ci update_crontab(fname, name); 2630f66f451Sopenharmony_ci unlink(tname); 2640f66f451Sopenharmony_ci} 2650f66f451Sopenharmony_ci 2660f66f451Sopenharmony_cistatic void do_edit(struct passwd *pwd) 2670f66f451Sopenharmony_ci{ 2680f66f451Sopenharmony_ci struct stat sb; 2690f66f451Sopenharmony_ci time_t mtime = 0; 2700f66f451Sopenharmony_ci int srcfd, destfd, status; 2710f66f451Sopenharmony_ci pid_t pid, cpid; 2720f66f451Sopenharmony_ci char tname[] = "/tmp/crontab.XXXXXX"; 2730f66f451Sopenharmony_ci 2740f66f451Sopenharmony_ci if ((destfd = mkstemp(tname)) < 0) 2750f66f451Sopenharmony_ci perror_exit("Can't open tmp file"); 2760f66f451Sopenharmony_ci 2770f66f451Sopenharmony_ci fchmod(destfd, 0666); 2780f66f451Sopenharmony_ci snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, pwd->pw_name); 2790f66f451Sopenharmony_ci 2800f66f451Sopenharmony_ci if (!stat(toybuf, &sb)) { // file exists and have some content. 2810f66f451Sopenharmony_ci if (sb.st_size) { 2820f66f451Sopenharmony_ci srcfd = xopenro(toybuf); 2830f66f451Sopenharmony_ci xsendfile(srcfd, destfd); 2840f66f451Sopenharmony_ci xclose(srcfd); 2850f66f451Sopenharmony_ci } 2860f66f451Sopenharmony_ci } else printf("No crontab for '%s'- using an empty one\n", pwd->pw_name); 2870f66f451Sopenharmony_ci xclose(destfd); 2880f66f451Sopenharmony_ci 2890f66f451Sopenharmony_ci if (!stat(tname, &sb)) mtime = sb.st_mtime; 2900f66f451Sopenharmony_ci 2910f66f451Sopenharmony_ciRETRY: 2920f66f451Sopenharmony_ci if (!(pid = xfork())) { 2930f66f451Sopenharmony_ci char *prog = pwd->pw_shell; 2940f66f451Sopenharmony_ci 2950f66f451Sopenharmony_ci xsetuser(pwd); 2960f66f451Sopenharmony_ci if (pwd->pw_uid) { 2970f66f451Sopenharmony_ci if (setenv("USER", pwd->pw_name, 1)) _exit(1); 2980f66f451Sopenharmony_ci if (setenv("LOGNAME", pwd->pw_name, 1)) _exit(1); 2990f66f451Sopenharmony_ci } 3000f66f451Sopenharmony_ci if (setenv("HOME", pwd->pw_dir, 1)) _exit(1); 3010f66f451Sopenharmony_ci if (setenv("SHELL",((!prog || !*prog) ? "/bin/sh" : prog), 1)) _exit(1); 3020f66f451Sopenharmony_ci 3030f66f451Sopenharmony_ci if (!(prog = getenv("VISUAL"))) { 3040f66f451Sopenharmony_ci if (!(prog = getenv("EDITOR"))) 3050f66f451Sopenharmony_ci prog = "vi"; 3060f66f451Sopenharmony_ci } 3070f66f451Sopenharmony_ci execlp(prog, prog, tname, (char *) NULL); 3080f66f451Sopenharmony_ci perror_exit("can't execute '%s'", prog); 3090f66f451Sopenharmony_ci } 3100f66f451Sopenharmony_ci 3110f66f451Sopenharmony_ci // Parent Process. 3120f66f451Sopenharmony_ci do { 3130f66f451Sopenharmony_ci cpid = waitpid(pid, &status, 0); 3140f66f451Sopenharmony_ci } while ((cpid == -1) && (errno == EINTR)); 3150f66f451Sopenharmony_ci 3160f66f451Sopenharmony_ci if (!stat(tname, &sb) && (mtime == sb.st_mtime)) { 3170f66f451Sopenharmony_ci printf("%s: no changes made to crontab\n", toys.which->name); 3180f66f451Sopenharmony_ci unlink(tname); 3190f66f451Sopenharmony_ci return; 3200f66f451Sopenharmony_ci } 3210f66f451Sopenharmony_ci printf("%s: installing new crontab\n", toys.which->name); 3220f66f451Sopenharmony_ci if (parse_crontab(tname)) { 3230f66f451Sopenharmony_ci fprintf(stderr, "errors in crontab file, can't install.\n" 3240f66f451Sopenharmony_ci "Do you want to retry the same edit? "); 3250f66f451Sopenharmony_ci if (!yesno(0)) { 3260f66f451Sopenharmony_ci error_msg("edits left in '%s'", tname); 3270f66f451Sopenharmony_ci return; 3280f66f451Sopenharmony_ci } 3290f66f451Sopenharmony_ci goto RETRY; 3300f66f451Sopenharmony_ci } 3310f66f451Sopenharmony_ci // parsing of crontab success; update the crontab. 3320f66f451Sopenharmony_ci update_crontab(tname, pwd->pw_name); 3330f66f451Sopenharmony_ci unlink(tname); 3340f66f451Sopenharmony_ci} 3350f66f451Sopenharmony_ci 3360f66f451Sopenharmony_civoid crontab_main(void) 3370f66f451Sopenharmony_ci{ 3380f66f451Sopenharmony_ci struct passwd *pwd = NULL; 3390f66f451Sopenharmony_ci long FLAG_elr = toys.optflags & (FLAG_e|FLAG_l|FLAG_r); 3400f66f451Sopenharmony_ci 3410f66f451Sopenharmony_ci if (TT.cdir && (TT.cdir[strlen(TT.cdir)-1] != '/')) 3420f66f451Sopenharmony_ci TT.cdir = xmprintf("%s/", TT.cdir); 3430f66f451Sopenharmony_ci if (!TT.cdir) TT.cdir = xstrdup("/var/spool/cron/crontabs/"); 3440f66f451Sopenharmony_ci 3450f66f451Sopenharmony_ci if (toys.optflags & FLAG_u) { 3460f66f451Sopenharmony_ci if (getuid()) error_exit("must be privileged to use -u"); 3470f66f451Sopenharmony_ci pwd = xgetpwnam(TT.user); 3480f66f451Sopenharmony_ci } else pwd = xgetpwuid(getuid()); 3490f66f451Sopenharmony_ci 3500f66f451Sopenharmony_ci if (!toys.optc) { 3510f66f451Sopenharmony_ci if (!FLAG_elr) { 3520f66f451Sopenharmony_ci if (toys.optflags & FLAG_u) 3530f66f451Sopenharmony_ci help_exit("file name must be specified for replace"); 3540f66f451Sopenharmony_ci do_replace(pwd->pw_name); 3550f66f451Sopenharmony_ci } 3560f66f451Sopenharmony_ci else if (toys.optflags & FLAG_e) do_edit(pwd); 3570f66f451Sopenharmony_ci else if (toys.optflags & FLAG_l) do_list(pwd->pw_name); 3580f66f451Sopenharmony_ci else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name); 3590f66f451Sopenharmony_ci } else { 3600f66f451Sopenharmony_ci if (FLAG_elr) help_exit("no arguments permitted after this option"); 3610f66f451Sopenharmony_ci do_replace(pwd->pw_name); 3620f66f451Sopenharmony_ci } 3630f66f451Sopenharmony_ci if (!(toys.optflags & FLAG_c)) free(TT.cdir); 3640f66f451Sopenharmony_ci} 365