io.cc 11 KB

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