io.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. // Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
  2. //
  3. // This program is free software; you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation; either version 2 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  16. #include <config.h>
  17. #include <stdarg.h>
  18. #include <fcntl.h> // file primitives
  19. #include <unistd.h>
  20. #include <sys/types.h>
  21. #include <sys/stat.h>
  22. #include <errno.h>
  23. #include <string.h> // strerror
  24. #include <pwd.h> // getpwuid
  25. #include <stdlib.h> // getenv
  26. #include <map>
  27. #include "io.h"
  28. #include "editbox.h"
  29. #include "converters.h"
  30. #include "dbg.h"
  31. #include "speller.h" // for UNLOAD_SPELLER(), see TODO
  32. #define CONVBUFSIZ 8192
  33. static u8string err_msg;
  34. void set_last_error(const char *fmt, ...)
  35. {
  36. va_list ap;
  37. va_start(ap, fmt);
  38. err_msg.vcformat(fmt, ap);
  39. va_end(ap);
  40. }
  41. void set_last_error(int err) {
  42. err_msg = strerror(err);
  43. }
  44. const char *get_last_error() {
  45. return err_msg.c_str();
  46. }
  47. static bool xload_file(EditBox *editbox,
  48. int fd,
  49. const char *specified_encoding,
  50. const char *default_encoding,
  51. u8string &effective_encoding)
  52. {
  53. unichar outbuf[CONVBUFSIZ+1];
  54. char inbuf[CONVBUFSIZ];
  55. bool result = true;
  56. const char *encoding = NULL; // silence the compiler
  57. Converter *conv = NULL;
  58. // we set "effective_encoding" here too, because the
  59. // file might be empty and the next assignment won't be
  60. // executed at all.
  61. if (specified_encoding && *specified_encoding)
  62. effective_encoding = specified_encoding;
  63. else
  64. effective_encoding = default_encoding;
  65. size_t insize = 0;
  66. size_t buf_file_offset = 0;
  67. while (1) {
  68. ssize_t nread;
  69. char *inptr = inbuf;
  70. nread = read(fd, inbuf + insize, sizeof(inbuf) - insize);
  71. if (nread == 0) {
  72. // no more input
  73. break;
  74. }
  75. if (nread == -1) {
  76. set_last_error(errno);
  77. result = false;
  78. break;
  79. }
  80. insize += nread;
  81. // instantiate a Converter object
  82. if (!conv) {
  83. if (!specified_encoding || !*specified_encoding) {
  84. const char *guess = guess_encoding(inbuf, insize);
  85. if (guess)
  86. encoding = guess;
  87. else
  88. encoding = default_encoding;
  89. } else {
  90. encoding = specified_encoding;
  91. }
  92. conv = ConverterFactory::get_converter_from(encoding);
  93. if (!conv) {
  94. set_last_error(_("Conversion from '%s' not available"),
  95. encoding);
  96. result = false;
  97. break;
  98. }
  99. effective_encoding = encoding;
  100. }
  101. unichar *wrptr = outbuf;
  102. // do the conversion
  103. int nconv = conv->convert(&wrptr, &inptr, insize);
  104. // load into editbox
  105. editbox->transfer_data(outbuf, wrptr - outbuf);
  106. if (nconv == -1) {
  107. insize = inbuf + insize - inptr;
  108. if (errno == EINVAL) {
  109. // incomplete byte sequence. move the unused bytes
  110. // to the beginning of the buffer. the next read()
  111. // will complete the sequence.
  112. memmove(inbuf, inptr, insize);
  113. } else {
  114. // invalid byte sequence.
  115. if (errno == EILSEQ)
  116. set_last_error(_("'%s' conversion failed at position %d"),
  117. encoding, buf_file_offset + (inptr - inbuf));
  118. else
  119. set_last_error(_("'%s' conversion failed"), encoding);
  120. result = false;
  121. break;
  122. }
  123. } else {
  124. insize = 0;
  125. }
  126. buf_file_offset += nread;
  127. }
  128. if (conv)
  129. delete conv;
  130. return result;
  131. }
  132. // xload_file() - loads a file into an EditBox buffer.
  133. bool xload_file(EditBox *editbox,
  134. const char *filename,
  135. const char *specified_encoding,
  136. const char *default_encoding,
  137. u8string &effective_encoding,
  138. bool &is_new,
  139. bool new_document)
  140. {
  141. int fd;
  142. bool is_pipe = false;
  143. FILE *pipe_stream = NULL;
  144. bool is_stdin = false;
  145. if (filename[0] == '-' && filename[1] == '\0')
  146. is_stdin = true;
  147. if (filename[0] == '|' || filename[0] == '!') {
  148. filename++;
  149. // we use UTF-8 for pipe communication
  150. specified_encoding = "UTF-8";
  151. is_pipe = true;
  152. }
  153. editbox->start_data_transfer(EditBox::dataTransferIn, new_document);
  154. if (is_pipe) {
  155. UNLOAD_SPELLER(); // see TODO
  156. is_new = true;
  157. pipe_stream = popen(filename, "r");
  158. if (pipe_stream == NULL) {
  159. editbox->end_data_transfer();
  160. set_last_error(errno);
  161. return false;
  162. }
  163. fd = fileno(pipe_stream);
  164. } else {
  165. is_new = false;
  166. if (is_stdin)
  167. fd = STDIN_FILENO;
  168. else
  169. fd = open(filename, O_RDONLY);
  170. if (fd == -1) {
  171. editbox->end_data_transfer();
  172. if (errno == ENOENT && new_document) {
  173. is_new = true;
  174. return true;
  175. } else {
  176. set_last_error(errno);
  177. return false;
  178. }
  179. }
  180. }
  181. bool result = xload_file(editbox, fd, specified_encoding,
  182. default_encoding, effective_encoding);
  183. editbox->end_data_transfer();
  184. if (is_pipe)
  185. pclose(pipe_stream);
  186. else {
  187. if (!is_stdin)
  188. close(fd);
  189. }
  190. return result;
  191. }
  192. static bool xsave_file(EditBox *editbox,
  193. int fd,
  194. const char *encoding,
  195. unichar &offending_char)
  196. {
  197. char outbuf[CONVBUFSIZ*6];
  198. unichar inbuf[CONVBUFSIZ];
  199. bool result = true;
  200. Converter *conv = NULL;
  201. conv = ConverterFactory::get_converter_to(encoding);
  202. if (!conv) {
  203. set_last_error(_("Conversion to '%s' not available"), encoding);
  204. return false;
  205. }
  206. int edit_buf_offset = 0;
  207. while (1) {
  208. int nread = editbox->transfer_data(inbuf, sizeof(inbuf)/sizeof(unichar));
  209. if (nread == 0) {
  210. // We reached end of buffer.
  211. // :TODO: zero output state.
  212. break;
  213. }
  214. char *wrptr = outbuf;
  215. unichar *inptr = inbuf;
  216. // do the conversion
  217. int nconv = conv->convert(&wrptr, &inptr, nread);
  218. if (write(fd, outbuf, wrptr - outbuf) == -1) {
  219. set_last_error(errno);
  220. result = false;
  221. break;
  222. }
  223. if (nconv == -1) {
  224. // Probably some unicode character couldn't be converted
  225. // to the requested encoding.
  226. if (errno == EILSEQ) {
  227. int illegal_pos = inptr - inbuf;
  228. offending_char = inbuf[illegal_pos];
  229. set_last_error(_("'%s' conversion failed at position "
  230. "%d (char: U+%04X)"),
  231. encoding, edit_buf_offset + illegal_pos,
  232. offending_char);
  233. } else {
  234. set_last_error(_("'%s' conversion failed"), encoding);
  235. }
  236. result = false;
  237. break;
  238. }
  239. edit_buf_offset += nread;
  240. }
  241. if (conv)
  242. delete conv;
  243. return result;
  244. }
  245. // xsave_file() - saves an EditBox buffer to a file.
  246. bool xsave_file(EditBox *editbox,
  247. const char *filename,
  248. const char *specified_encoding,
  249. const char *backup_suffix,
  250. unichar &offending_char,
  251. bool selection_only)
  252. {
  253. offending_char = 0;
  254. int fd;
  255. bool is_pipe = false;
  256. FILE *pipe_stream = NULL;
  257. u8string tmp_filename;
  258. bool is_stdout = false;
  259. if (filename[0] == '-' && filename[1] == '\0')
  260. is_stdout = true;
  261. if (filename[0] == '|' || filename[0] == '!') {
  262. filename++;
  263. // we use UTF-8 for pipe communication
  264. specified_encoding = "UTF-8";
  265. is_pipe = true;
  266. }
  267. if (is_pipe) {
  268. UNLOAD_SPELLER(); // see TODO
  269. pipe_stream = popen(filename, "w");
  270. if (pipe_stream == NULL) {
  271. set_last_error(errno);
  272. return false;
  273. }
  274. fd = fileno(pipe_stream);
  275. } else if (is_stdout) {
  276. fd = STDOUT_FILENO;
  277. } else {
  278. // 1. Get filename's permissions, if exists.
  279. struct stat file_info;
  280. mode_t permissions;
  281. if (stat(filename, &file_info) != -1)
  282. permissions = (file_info.st_mode & 0777);
  283. else
  284. permissions = 0666;
  285. // 2. Create filename.tmp and write data into it.
  286. // Use filename's permissions.
  287. tmp_filename = filename;
  288. tmp_filename += ".tmp";
  289. fd = open(tmp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
  290. permissions);
  291. if (fd == -1) {
  292. set_last_error(errno);
  293. return false;
  294. }
  295. }
  296. editbox->start_data_transfer(EditBox::dataTransferOut, false, selection_only);
  297. bool result = xsave_file(editbox, fd, specified_encoding, offending_char);
  298. editbox->end_data_transfer();
  299. if (is_pipe) {
  300. pclose(pipe_stream);
  301. } else if (is_stdout) {
  302. if (!result) {
  303. return false;
  304. }
  305. } else {
  306. if (!result) {
  307. close(fd);
  308. unlink(tmp_filename.c_str());
  309. return false;
  310. }
  311. if (close(fd) == -1) {
  312. set_last_error(errno);
  313. unlink(tmp_filename.c_str());
  314. return false;
  315. }
  316. // 3. if the user wants a backup,
  317. // mv filename -> filename${backup_extension}
  318. // We ignore errors, since filename may not exist.
  319. if (backup_suffix && *backup_suffix) {
  320. u8string backup = filename;
  321. backup += backup_suffix;
  322. rename(filename, backup.c_str());
  323. }
  324. // Else, unlink filename
  325. else {
  326. unlink(filename);
  327. }
  328. // 4. rename filename.tmp to filename
  329. if (rename(tmp_filename.c_str(), filename) == -1) {
  330. set_last_error(errno);
  331. return false;
  332. }
  333. }
  334. return true;
  335. }
  336. // has_prog() returns true if progname is in the PATH and is executable.
  337. bool has_prog(const char *progname)
  338. {
  339. const char *path = getenv("PATH");
  340. if (!path) return false;
  341. const char *pos = path;
  342. while (pos) {
  343. u8string comp;
  344. pos = strchr(path, ':');
  345. if (!pos)
  346. comp = u8string(path);
  347. else
  348. comp = u8string(path, pos);
  349. comp += u8string("/") + progname;
  350. if (access(comp.c_str(), X_OK) == 0) {
  351. //cerr << "found [" << comp.c_str() << "]\n";
  352. return true;
  353. }
  354. path = pos + 1;
  355. }
  356. return false;
  357. }
  358. // expand_tilde() - do tilde expansion in filenames.
  359. void expand_tilde(u8string &filename)
  360. {
  361. int slash_pos = filename.index('/');
  362. if (slash_pos == -1 || filename[0] != '~')
  363. return; // nothing to do
  364. u8string tilde_prefix(&*(filename.begin() + 1),
  365. &*(filename.begin() + slash_pos));
  366. u8string user_home;
  367. if (tilde_prefix == "") {
  368. // handle "~/" - the user executing us.
  369. if (getenv("HOME"))
  370. user_home = getenv("HOME");
  371. else
  372. user_home = getpwuid(geteuid())->pw_dir;
  373. } else {
  374. // handle "~username/"
  375. struct passwd *user_info;
  376. if ((user_info = getpwnam(tilde_prefix.c_str()))) {
  377. user_home = user_info->pw_dir;
  378. }
  379. }
  380. // if we've found the user's home, replace tilde_prefix with it.
  381. if (!user_home.empty()) {
  382. if (*(user_home.end() - 1) != '/')
  383. user_home += "/";
  384. filename.replace(0, tilde_prefix.size() + 2, user_home);
  385. }
  386. }
  387. struct ltstr {
  388. bool operator() (const char *s1, const char *s2) const {
  389. return strcmp(s1, s2) < 0;
  390. }
  391. };
  392. // get_cfg_filename() - does "%P" and tilde expansion on a package
  393. // pathname. in other words, converts a package pathname (~/%P/name)
  394. // to a filesystem pathname (/home/david/geresh/name).
  395. const char *get_cfg_filename(const char *tmplt)
  396. {
  397. // save the expansion result in a hash
  398. static std::map<const char *, u8string, ltstr> filenames_cache;
  399. if (filenames_cache.find(tmplt) == filenames_cache.end()) {
  400. u8string filename = tmplt;
  401. // replace all "%P" with PACKAGE
  402. const char *packpos;
  403. while ((packpos = strstr(filename.c_str(), "%P")) != NULL)
  404. filename.replace(packpos - filename.c_str(), 2, PACKAGE);
  405. expand_tilde(filename);
  406. filenames_cache[tmplt] = filename;
  407. }
  408. return filenames_cache[tmplt].c_str();
  409. }