/* * UNG's Not GNU * * Copyright (c) 2022, Jakob Kaivo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include enum times { ACCESS = (1<<0), MODIFY = (1<<1), }; static int touch(const char *path, struct timespec times[2], int create) { if (access(path, F_OK) == 0) { return utimensat(AT_FDCWD, path, times, 0); } if (!create) { return 0; } int fd = creat(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd == -1) { fprintf(stderr, "touch: %s: %s\n", path, strerror(errno)); return 1; } if (futimens(fd, times) != 0) { fprintf(stderr, "touch: %s: %s\n", path, strerror(errno)); close(fd); return 1; } return 0; } static int twodigits(const char *s, int min, int max) { char buf[3] = { s[0], s[1], '\0' }; if (!(isdigit(buf[0]) && isdigit(buf[1]))) { fprintf(stderr, "touch: invalid time\n"); exit(1); } int ret = atoi(buf); if (ret < min || ret > max) { fprintf(stderr, "touch: invalid time\n"); exit(1); } return ret; } static struct timespec convert_time(const char *date) { struct timespec ts = { 0 }; time_t now = time(NULL); struct tm *tm = localtime(&now); tm->tm_yday = 0; tm->tm_wday = 0; int century = -1; char *dot = strchr(date, '.'); if (dot) { if (strlen(dot + 1) != 2) { fprintf(stderr, "invalid time\n"); exit(1); } tm->tm_sec = twodigits(dot + 1, 0, 60); } else { tm->tm_sec = 0; } switch (strlen(date) - (dot ? 3 : 0)) { case 12: /* CC */ century = twodigits(date, 0, 100) - 19; date += 2; /* FALLTHRU */ case 10: /* YY */ if (century == -1) { tm->tm_year = twodigits(date, 0, 100); if (00 <= tm->tm_year && tm->tm_year <= 68) { tm->tm_year += 100; } } else { tm->tm_year = (century * 100) + twodigits(date, 0, 100); } date += 2; /* FALLTHRU */ case 8: /* MM */ tm->tm_mon = twodigits(date, 1, 12) - 1; date += 2; tm->tm_mday = twodigits(date, 1, 31); date += 2; tm->tm_hour = twodigits(date, 0, 23); date += 2; tm->tm_min = twodigits(date, 0, 59); break; default: fprintf(stderr, "invalid time\n"); exit(1); } /* TODO: account for TZ */ /* TODO: time too great to represent */ ts.tv_sec = mktime(tm); return ts; } static int getnext(const char *current, char **next, size_t ndigits, char *term, int min, int max) { char buf[ndigits + 1]; for (size_t i = 0; i < ndigits; i++) { if (!isdigit(current[i])) { return -1; } buf[i] = current[i]; } buf[ndigits] = '\0'; char *end = NULL; long ret = strtol(buf, &end, 10); if (!strchr(term, *end)) { fprintf(stderr, "touch: invalid date\n"); exit(1); } if (ret < min || ret > max) { fprintf(stderr, "touch: invalid date\n"); exit(1); } *next = (char*)current + ndigits + 1; return ret; } static struct timespec convert_date(char *date) { struct timespec ts = { 0 }; struct tm tm = { 0 }; char *next = (char*)date; tm.tm_year = getnext(next, &next, 4, "-", 0, 10000) - 1900; tm.tm_mon = getnext(next, &next, 2, "-", 1, 12) - 1; tm.tm_mday = getnext(next, &next, 2, "T ", 1, 31); tm.tm_hour = getnext(next, &next, 2, ":", 0, 23); tm.tm_min = getnext(next, &next, 2, ":", 0, 59); tm.tm_sec = getnext(next, &next, 2, ".,", 0, 60); char *dec = strchr(date, '.') ? strchr(date, '.') : strchr(date, ','); if (dec) { *dec = '.'; double d = strtod(dec, &next); ts.tv_nsec = d * 1000000000; } /* TODO: tz */ /* TODO: time is too large */ ts.tv_sec = mktime(&tm); return ts; } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); enum times which = 0; int create = 1; enum { CURRENT, TIME, DATE, FILE } how = CURRENT; char *source = NULL; int c; while ((c = getopt(argc, argv, "acmr:t:d:")) != -1) { switch(c) { case 'a': which |= ACCESS; break; case 'c': create = 0; break; case 'm': which |= MODIFY; break; case 'd': if (how != CURRENT) { fprintf(stderr, "touch: only one of -d, -r, and -t is allowed\n"); return 1; } how = DATE; source = optarg; break; case 'r': if (how != CURRENT) { fprintf(stderr, "touch: only one of -d, -r, and -t is allowed\n"); return 1; } how = FILE; source = optarg; break; case 't': if (how != CURRENT) { fprintf(stderr, "touch: only one of -d, -r, and -t is allowed\n"); return 1; } how = TIME; source = optarg; break; default: return 1; } } if (optind >= argc) { fprintf(stderr, "touch: missing operands\n"); return 1; } struct timespec times[2] = { { 0 } }; struct stat st; switch (how) { case CURRENT: times[0].tv_nsec = UTIME_NOW; times[1].tv_nsec = UTIME_NOW; break; case DATE: times[0] = times[1] = convert_date(source); break; case TIME: times[0] = times[1] = convert_time(source); break; case FILE: if (stat(source, &st) != 0) { fprintf(stderr, "touch: %s: %s\n", source, strerror(errno)); return 1; } times[0] = st.st_atim; times[1] = st.st_mtim; } if (which == 0) { which = ACCESS | MODIFY; } if (!(which & ACCESS)) { times[0].tv_nsec = UTIME_OMIT; } if (!(which & MODIFY)) { times[1].tv_nsec = UTIME_OMIT; } int ret = 0; do { ret |= touch(argv[optind++], times, create); } while (optind < argc); return ret; }