Version 1.10.0.
[libguestfs.git] / fish / hexedit.c
1 /* guestfish - the filesystem interactive shell
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <inttypes.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29
30 #include "fish.h"
31
32 #define MAX_DOWNLOAD_SIZE (16 * 1024 * 1024)
33 #define MAX_DOWNLOAD_SIZE_TEXT "16MB"
34
35 static off_t get_size (const char *filename);
36
37 int
38 run_hexedit (const char *cmd, size_t argc, char *argv[])
39 {
40   if (argc < 1 || argc > 3) {
41     fprintf (stderr, _("hexedit (device|filename) [max | start max]\n"));
42     return -1;
43   }
44
45   const char *filename = argv[0];
46   off_t size = get_size (filename);
47   if (size == -1)
48     return -1;
49
50   if (size == 0) {
51     fprintf (stderr,
52              _("hexedit: %s is a zero length file or device\n"), filename);
53     return -1;
54   }
55
56   off_t start;
57   off_t max;
58
59   if (argc == 1) {              /* hexedit device */
60     /* Check we're not going to download a huge file. */
61     if (size > MAX_DOWNLOAD_SIZE) {
62       fprintf (stderr,
63          _("hexedit: %s is larger than %s. You must supply a limit using\n"
64            "  'hexedit %s <max>' (eg. 'hexedit %s 1M') or a range using\n"
65            "  'hexedit %s <start> <max>'.\n"),
66                filename, MAX_DOWNLOAD_SIZE_TEXT,
67                filename, filename,
68                filename);
69       return -1;
70     }
71
72     start = 0;
73     max = size;
74   }
75   else {
76     if (argc == 3) {            /* hexedit device start max */
77       if (parse_size (argv[1], &start) == -1)
78         return -1;
79       if (parse_size (argv[2], &max) == -1)
80         return -1;
81     } else {                    /* hexedit device max */
82       start = 0;
83       if (parse_size (argv[1], &max) == -1)
84         return -1;
85     }
86
87     if (start + max > size)
88       max = size - start;
89   }
90
91   if (max <= 0) {
92     fprintf (stderr, _("hexedit: invalid range\n"));
93     return -1;
94   }
95
96   /* Download the requested range from the remote file|device into a
97    * local temporary file.
98    */
99   const char *editor;
100   int r;
101   struct stat oldstat, newstat;
102   char buf[BUFSIZ];
103   TMP_TEMPLATE_ON_STACK (tmp);
104   int fd = mkstemp (tmp);
105   if (fd == -1) {
106     perror ("mkstemp");
107     return -1;
108   }
109
110   /* Choose an editor. */
111   editor = getenv ("HEXEDITOR");
112   if (editor == NULL)
113     editor = "hexedit";
114
115   snprintf (buf, sizeof buf, "/dev/fd/%d", fd);
116
117   if (guestfs_download_offset (g, filename, buf, start, max) == -1) {
118     unlink (tmp);
119     close (fd);
120     return -1;
121   }
122
123   if (close (fd) == -1) {
124     unlink (tmp);
125     return -1;
126   }
127
128   /* Get the old stat. */
129   if (stat (tmp, &oldstat) == -1) {
130     perror (tmp);
131     unlink (tmp);
132     return -1;
133   }
134
135   /* Edit it. */
136   snprintf (buf, sizeof buf, "%s %s", editor, tmp);
137
138   r = system (buf);
139   if (r != 0) {
140     perror (buf);
141     unlink (tmp);
142     return -1;
143   }
144
145   /* Get the new stat. */
146   if (stat (tmp, &newstat) == -1) {
147     perror (tmp);
148     unlink (tmp);
149     return -1;
150   }
151
152   /* Changed? */
153   if (oldstat.st_ctime == newstat.st_ctime &&
154       oldstat.st_size == newstat.st_size) {
155     unlink (tmp);
156     return 0;
157   }
158
159   /* Write new content. */
160   if (guestfs_upload_offset (g, tmp, filename, start) == -1) {
161     unlink (tmp);
162     return -1;
163   }
164
165   unlink (tmp);
166   return 0;
167 }
168
169 /* Get the size of the file or block device. */
170 static off_t
171 get_size (const char *filename)
172 {
173   int64_t size;
174
175   if (STRPREFIX (filename, "/dev/")) {
176     size = guestfs_blockdev_getsize64 (g, filename);
177     if (size == -1)
178       return -1;
179   }
180   else {
181     size = guestfs_filesize (g, filename);
182     if (size == -1)
183       return -1;
184   }
185
186   /* This case should be safe because we always compile with
187    * 64 bit file offsets.
188    */
189   return (off_t) size;
190 }