tar.c 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /* vim: ts=4 sw=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2018 K. Lange
  5. *
  6. * tar - extract archives
  7. *
  8. * This is a very minimal and incomplete implementation of tar.
  9. * It supports on ustar-formatted archives, and its arguments
  10. * must by the - forms. As of writing, creating archives is not
  11. * supported. No compression formats are supported, either.
  12. */
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <unistd.h>
  16. #include <getopt.h>
  17. #include <errno.h>
  18. #include <fcntl.h>
  19. #include <sys/stat.h>
  20. #include <sys/types.h>
  21. #include <toaru/hashmap.h>
  22. struct ustar {
  23. char filename[100];
  24. char mode[8];
  25. char ownerid[8];
  26. char groupid[8];
  27. char size[12];
  28. char mtime[12];
  29. char checksum[8];
  30. char type[1];
  31. char link[100];
  32. char ustar[6];
  33. char version[2];
  34. char owner[32];
  35. char group[32];
  36. char dev_major[8];
  37. char dev_minor[8];
  38. char prefix[155];
  39. };
  40. static struct ustar * file_from_offset(FILE * f, size_t offset) {
  41. static struct ustar _ustar;
  42. fseek(f, offset, SEEK_SET);
  43. if (fread(&_ustar, 1, sizeof(struct ustar), f) != sizeof(struct ustar)) {
  44. fprintf(stderr, "failed to read file\n");
  45. return NULL;
  46. }
  47. if (_ustar.ustar[0] != 'u' ||
  48. _ustar.ustar[1] != 's' ||
  49. _ustar.ustar[2] != 't' ||
  50. _ustar.ustar[3] != 'a' ||
  51. _ustar.ustar[4] != 'r') {
  52. return NULL;
  53. }
  54. return &_ustar;
  55. }
  56. static unsigned int round_to_512(unsigned int i) {
  57. unsigned int t = i % 512;
  58. if (!t) return i;
  59. return i + (512 - t);
  60. }
  61. static unsigned int interpret_mode(struct ustar * file) {
  62. return
  63. ((file->mode[0] - '0') << 18) |
  64. ((file->mode[1] - '0') << 15) |
  65. ((file->mode[2] - '0') << 12) |
  66. ((file->mode[3] - '0') << 9) |
  67. ((file->mode[4] - '0') << 6) |
  68. ((file->mode[5] - '0') << 3) |
  69. ((file->mode[6] - '0') << 0);
  70. }
  71. static unsigned int interpret_size(struct ustar * file) {
  72. if (file->size[0] != '0') {
  73. fprintf(stderr, "\033[3;32mWarning:\033[0;3m File is too big.\033[0m\n");
  74. }
  75. return
  76. ((file->size[ 0] - '0') << 30) |
  77. ((file->size[ 1] - '0') << 27) |
  78. ((file->size[ 2] - '0') << 24) |
  79. ((file->size[ 3] - '0') << 21) |
  80. ((file->size[ 4] - '0') << 18) |
  81. ((file->size[ 5] - '0') << 15) |
  82. ((file->size[ 6] - '0') << 12) |
  83. ((file->size[ 7] - '0') << 9) |
  84. ((file->size[ 8] - '0') << 6) |
  85. ((file->size[ 9] - '0') << 3) |
  86. ((file->size[10] - '0') << 0);
  87. }
  88. static const char * type_to_string(char type) {
  89. switch (type) {
  90. case '\0':
  91. case '0':
  92. return "Normal file";
  93. case '1':
  94. return "Hard link (unsupported)";
  95. case '2':
  96. return "Symolic link";
  97. case '3':
  98. return "Character special (unsupported)";
  99. case '4':
  100. return "Block special (unsupported)";
  101. case '5':
  102. return "Directory";
  103. case '6':
  104. return "FIFO (unsupported)";
  105. case 'g':
  106. return "Extended header";
  107. case 'x':
  108. return "Extended preheader";
  109. default:
  110. return "Unknown";
  111. }
  112. }
  113. #if 0
  114. static void dump_file(struct ustar * file) {
  115. fprintf(stdout, "\033[1m%.155s%.100s\033[0m\n", file->prefix, file->filename);
  116. fprintf(stdout, "%c - %s\n", file->type[0], type_to_string(file->type[0]));
  117. fprintf(stdout, "File size: %u\n", interpret_size(file));
  118. }
  119. #endif
  120. #define CHUNK_SIZE 4096
  121. static void write_file(struct ustar * file, FILE * f, FILE * mf, size_t off, char * name) {
  122. size_t length = interpret_size(file);
  123. fseek(f, off + 512, SEEK_SET);
  124. char buf[CHUNK_SIZE];
  125. while (length > CHUNK_SIZE) {
  126. fread( buf, 1, CHUNK_SIZE, f);
  127. fwrite(buf, 1, CHUNK_SIZE, mf);
  128. length -= CHUNK_SIZE;
  129. }
  130. if (length > 0) {
  131. fread( buf, 1, length, f);
  132. fwrite(buf, 1, length, mf);
  133. }
  134. fclose(mf);
  135. fseek(f, off, SEEK_SET);
  136. /* TODO: fchmod? */
  137. chmod(name, interpret_mode(file));
  138. }
  139. int main(int argc, char * argv[]) {
  140. int opt;
  141. char * fname = NULL;
  142. int verbose = 0;
  143. int action = 0;
  144. #define TAR_ACTION_EXTRACT 1
  145. #define TAR_ACTION_CREATE 2
  146. #define TAR_ACTION_LIST 3
  147. while ((opt = getopt(argc, argv, "ctxvaf:")) != -1) {
  148. switch (opt) {
  149. case 'c':
  150. if (action) {
  151. fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt);
  152. return 1;
  153. }
  154. action = TAR_ACTION_CREATE;
  155. break;
  156. case 'f':
  157. fname = optarg;
  158. break;
  159. case 'x':
  160. if (action) {
  161. fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt);
  162. return 1;
  163. }
  164. action = TAR_ACTION_EXTRACT;
  165. break;
  166. case 't':
  167. if (action) {
  168. fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt);
  169. return 1;
  170. }
  171. action = TAR_ACTION_LIST;
  172. break;
  173. case 'v':
  174. verbose = 1;
  175. break;
  176. default:
  177. fprintf(stderr, "%s: unsupported option '%c'\n", argv[0], opt);
  178. return 1;
  179. }
  180. }
  181. if (!fname) {
  182. fprintf(stderr, "%s: todo: stdin/stdout\n", argv[0]);
  183. return 1;
  184. }
  185. if (action == TAR_ACTION_EXTRACT || action == TAR_ACTION_LIST) {
  186. hashmap_t * files = hashmap_create(10);
  187. FILE * f = fopen(fname,"r");
  188. if (!f) {
  189. fprintf(stderr, "%s: %s: %s\n", argv[0], fname, strerror(errno));
  190. return 1;
  191. }
  192. fseek(f, 0, SEEK_END);
  193. size_t length = ftell(f);
  194. fseek(f, 0, SEEK_SET);
  195. size_t off = 0;
  196. while (!feof(f)) {
  197. struct ustar * file = file_from_offset(f, off);
  198. if (!file) {
  199. break;
  200. }
  201. if (action == TAR_ACTION_LIST || verbose) {
  202. fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename);
  203. }
  204. if (action == TAR_ACTION_EXTRACT) {
  205. char name[256] = {0};
  206. strncat(name, file->prefix, 155);
  207. strncat(name, file->filename, 100);
  208. if (file->type[0] == '0' || file->type[0] == 0) {
  209. FILE * mf = fopen(name,"w");
  210. if (!mf) {
  211. fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno));
  212. } else {
  213. write_file(file,f,mf,off,name);
  214. }
  215. struct ustar * tmp = malloc(sizeof(struct ustar));
  216. memcpy(tmp, file, sizeof(struct ustar));
  217. hashmap_set(files, name, tmp);
  218. } else if (file->type[0] == '5') {
  219. if (name[strlen(name)-1] == '/') {
  220. name[strlen(name)-1] = '\0';
  221. }
  222. if (strlen(name)) {
  223. if (mkdir(name, 0777) < 0) {
  224. fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno));
  225. }
  226. }
  227. } else if (file->type[0] == '1') {
  228. char tmp[101] = {0};
  229. strncat(tmp, file->link, 100);
  230. if (!hashmap_has(files, tmp)) {
  231. fprintf(stderr, "%s: %s: %s: %s: missing target\n", argv[0], fname, name, tmp);
  232. } else {
  233. FILE * mf = fopen(name,"w");
  234. if (!mf) {
  235. fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno));
  236. } else {
  237. write_file(hashmap_get(files,tmp),f,mf,off,name);
  238. }
  239. }
  240. } else if (file->type[0] == '2') {
  241. char tmp[101] = {0};
  242. strncat(tmp, file->link, 100);
  243. if (symlink(tmp, name) < 0) {
  244. fprintf(stderr, "%s: %s: %s: %s: %s\n", argv[0], fname, name, tmp, strerror(errno));
  245. }
  246. } else {
  247. fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, type_to_string(file->type[0]));
  248. }
  249. }
  250. off += 512;
  251. off += round_to_512(interpret_size(file));
  252. if (off >= length) break;
  253. }
  254. } else {
  255. fprintf(stderr, "%s: unsupported action\n", argv[0]);
  256. return 1;
  257. }
  258. return 0;
  259. }