1 /*
2  * os_os2.c
3  *
4  * Home page of code is: http://www.smartmontools.org
5  *
6  * Copyright (C) 2004-8 Yuri Dario
7  *
8  * SPDX-License-Identifier: GPL-2.0-or-later
9  */
10 
11 /*
12  *
13  * Thanks to Daniela Engert for providing sample code for SMART ioctl access.
14  *
15  */
16 
17 // These are needed to define prototypes for the functions defined below
18 #include "config.h"
19 
20 #include <ctype.h>
21 #include <errno.h>
22 #include "atacmds.h"
23 #include "scsicmds.h"
24 #include "utility.h"
25 
26 // This is to include whatever prototypes you define in os_generic.h
27 #include "os_os2.h"
28 
29 // Needed by '-V' option (CVS versioning) of smartd/smartctl
30 const char *os_XXXX_c_cvsid="$Id: os_os2.cpp 4842 2018-12-02 16:07:26Z chrfranke $" \
31 ATACMDS_H_CVSID OS_XXXX_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID;
32 
33 // global handle to device driver
34 static HFILE hDevice;
35 
36 // print examples for smartctl.  You should modify this function so
37 // that the device paths are sensible for your OS, and to eliminate
38 // unsupported commands (eg, 3ware controllers).
print_smartctl_examples()39 void print_smartctl_examples(){
40   printf("=================================================== SMARTCTL EXAMPLES =====\n\n"
41          "  smartctl -a hd0                       (Prints all SMART information)\n\n"
42          "  smartctl --smart=on --offlineauto=on --saveauto=on hd0\n"
43          "                                              (Enables SMART on first disk)\n\n"
44          "  smartctl -t long hd0              (Executes extended disk self-test)\n\n"
45          "  smartctl --attributes --log=selftest --quietmode=errorsonly hd0\n"
46          "                                      (Prints Self-Test & Attribute errors)\n"
47          );
48   return;
49 }
50 
skipdev(const char * s)51 static const char * skipdev(const char * s)
52 {
53 	return (!strncmp(s, "/dev/", 5) ? s + 5 : s);
54 }
55 
56 // tries to guess device type given the name (a path).  See utility.h
57 // for return values.
guess_device_type(const char * dev_name)58 int guess_device_type (const char* dev_name) {
59 
60    //printf( "dev_name %s\n", dev_name);
61    dev_name = skipdev(dev_name);
62 	if (!strncmp(dev_name, "hd", 2) || !strncmp(dev_name, "ahci", 4))
63 		return CONTROLLER_ATA;
64   return CONTROLLER_UNKNOWN;
65 }
66 
67 // makes a list of ATA or SCSI devices for the DEVICESCAN directive of
68 // smartd.  Returns number N of devices, or -1 if out of
69 // memory. Allocates N+1 arrays: one of N pointers (devlist); the
70 // other N arrays each contain null-terminated character strings.  In
71 // the case N==0, no arrays are allocated because the array of 0
72 // pointers has zero length, equivalent to calling malloc(0).
73 
make_device_names(char *** devlist,const char * name)74 int make_device_names (char*** devlist, const char* name) {
75 
76   int result;
77   int index;
78   const int max_dev = 32; // scan only first 32 devices
79 
80   // SCSI is not supported
81   if (strcmp (name, "ATA") != 0)
82     return 0;
83 
84   // try to open DANIS
85   APIRET rc;
86   ULONG ActionTaken;
87   HFILE danisDev, ahciDev;
88   bool is_danis = 0, is_ahci = 0;
89 
90   rc = DosOpen ((const char unsigned *)danisdev, &danisDev, &ActionTaken, 0,  FILE_SYSTEM,
91 	       OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
92 	       OPEN_FLAGS_NOINHERIT | OPEN_ACCESS_READONLY, NULL);
93   if (!rc)
94     is_danis = 1;
95 
96   rc = DosOpen ((const char unsigned *)ahcidev, &ahciDev, &ActionTaken, 0,  FILE_SYSTEM,
97 	       OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
98 	       OPEN_FLAGS_NOINHERIT | OPEN_ACCESS_READONLY, NULL);
99   if (!rc)
100     is_ahci = 1;
101 
102   // Count the devices.
103   result = 0;
104 
105   DSKSP_CommandParameters Parms;
106   ULONG PLen = 1;
107   ULONG IDLen = 512;
108   struct ata_identify_device Id;
109 
110   for(int i = 0; i < max_dev; i++) {
111     if (is_ahci) {
112       Parms.byPhysicalUnit = i;
113       rc = DosDevIOCtl (ahciDev, DSKSP_CAT_GENERIC, DSKSP_GET_INQUIRY_DATA,
114    		     (PVOID)&Parms, PLen, &PLen, (PVOID)&Id, IDLen, &IDLen);
115       if (!rc) result++;
116     }
117     if (is_danis) {
118       Parms.byPhysicalUnit = i + 0x80;
119       rc = DosDevIOCtl (danisDev, DSKSP_CAT_GENERIC, DSKSP_GET_INQUIRY_DATA,
120    		     (PVOID)&Parms, PLen, &PLen, (PVOID)&Id, IDLen, &IDLen);
121       if (!rc) result++;
122     }
123   }
124   *devlist = (char**)calloc (result, sizeof (char *));
125   if (! *devlist)
126     goto error;
127   index = 0;
128 
129   // add devices
130   for(int i = 0; i < max_dev; i++) {
131     if (is_ahci) {
132       Parms.byPhysicalUnit = i;
133       rc = DosDevIOCtl (ahciDev, DSKSP_CAT_GENERIC, DSKSP_GET_INQUIRY_DATA,
134    		     (PVOID)&Parms, PLen, &PLen, (PVOID)&Id, IDLen, &IDLen);
135       if (!rc) {
136         asprintf(&(*devlist)[index], "ahci%d", i);
137         if (! (*devlist)[index])
138           goto error;
139         index++;
140       }
141     }
142     if (is_danis) {
143       Parms.byPhysicalUnit = i + 0x80;
144       rc = DosDevIOCtl (danisDev, DSKSP_CAT_GENERIC, DSKSP_GET_INQUIRY_DATA,
145    		     (PVOID)&Parms, PLen, &PLen, (PVOID)&Id, IDLen, &IDLen);
146       if (!rc) {
147         asprintf(&(*devlist)[index], "hd%d", i);
148         if (! (*devlist)[index])
149           goto error;
150         index++;
151       }
152     }
153   }
154 
155   if (is_danis)
156       DosClose( danisDev);
157 
158   if (is_ahci)
159       DosClose( ahciDev);
160 
161   return result;
162 
163  error:
164   if (*devlist)
165     {
166       for (index = 0; index < result; index++)
167         if ((*devlist)[index])
168           free ((*devlist)[index]);
169       free (*devlist);
170     }
171   if (is_danis)
172       DosClose( danisDev);
173 
174   if (is_ahci)
175       DosClose( ahciDev);
176 
177   return -1;
178 }
179 
180 // Like open().  Return non-negative integer handle, only used by the
181 // functions below.  type=="ATA" or "SCSI".  If you need to store
182 // extra information about your devices, create a private internal
183 // array within this file (see os_freebsd.cpp for an example).  If you
184 // can not open the device (permission denied, does not exist, etc)
185 // set errno as open() does and return <0.
deviceopen(const char * pathname,char *)186 int deviceopen(const char *pathname, char * /* type */ ){
187 
188   int fd = 0;
189   APIRET rc;
190   ULONG ActionTaken;
191 
192   char * activedev = NULL;
193 
194   pathname = skipdev(pathname);
195   // DANIS506 driver
196   if(strlen(pathname) > strlen(danispref)
197     && strncmp(pathname, danispref, strlen(danispref)) == 0) {
198     fd = strtol(pathname + strlen(danispref), NULL, 10) + 0x80;
199     activedev = (char *)danisdev;
200   }
201   // OS2AHCI driver
202   if(strlen(pathname) > strlen(ahcipref)
203     && strncmp(pathname, ahcipref, strlen(ahcipref)) == 0) {
204     fd = strtol(pathname + strlen(ahcipref), NULL, 10);
205     activedev = (char *)ahcidev;
206   }
207 
208   if(!activedev) {
209      pout("Error: please specify hdX or ahciX device name\n");
210      return -1;
211   }
212   //printf( "deviceopen pathname %s\n", pathname);
213   rc = DosOpen ((const char unsigned *)activedev, &hDevice, &ActionTaken, 0,  FILE_SYSTEM,
214 	       OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
215 	       OPEN_FLAGS_NOINHERIT | OPEN_ACCESS_READONLY, NULL);
216   if (rc) {
217     char errmsg[256];
218     snprintf(errmsg,256,"Smartctl open driver %s failed (%lu)", activedev, rc);
219     errmsg[255]='\0';
220     syserror(errmsg);
221     return -1;
222   }
223 
224   return fd;
225 }
226 
227 // Like close().  Acts only on integer handles returned by
228 // deviceopen() above.
deviceclose(int)229 int deviceclose(int /* fd */){
230 
231   DosClose( hDevice);
232   hDevice = NULL;
233 
234   return 0;
235 }
236 
237 //
238 // OS/2 direct ioctl interface to IBMS506$/OS2AHCI$
239 //
dani_ioctl(int device,void * arg)240 static int dani_ioctl( int device, void* arg)
241 {
242    unsigned char* buff = (unsigned char*) arg;
243    APIRET rc;
244    DSKSP_CommandParameters Parms;
245    ULONG PLen = 1;
246    ULONG DLen = 512; //sizeof (*buf);
247    ULONG value = 0;
248 
249    // printf( "device %d, request 0x%x, arg[0] 0x%x, arg[2] 0x%x\n", device, request, buff[0], buff[2]);
250 
251    Parms.byPhysicalUnit = device;
252    switch( buff[0]) {
253    case ATA_IDENTIFY_DEVICE:
254       rc = DosDevIOCtl (hDevice, DSKSP_CAT_GENERIC, DSKSP_GET_INQUIRY_DATA,
255    		     (PVOID)&Parms, PLen, &PLen, (UCHAR *)arg+4, DLen, &DLen);
256       if (rc != 0)
257       {
258           printf ("DANIS506 ATA DSKSP_GET_INQUIRY_DATA failed (%lu)\n", rc);
259           return -1;
260       }
261       break;
262    case ATA_SMART_CMD:
263       switch( buff[2]) {
264       case ATA_SMART_STATUS:
265          DLen = sizeof(value);
266          // OS/2 already checks CL/CH in IBM1S506 code!! see s506rte.c (ddk)
267          // value: -1=not supported, 0=ok, 1=failing
268          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_GETSTATUS,
269       		     (PVOID)&Parms, PLen, &PLen, (PVOID)&value, DLen, &DLen);
270          if (rc)
271          {
272              printf ("DANIS506 ATA GET SMART_STATUS failed (%lu)\n", rc);
273              return -1;
274          }
275          buff[4] = (unsigned char)value;
276          break;
277       case ATA_SMART_READ_VALUES:
278          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_GET_ATTRIBUTES,
279       		     (PVOID)&Parms, PLen, &PLen, (UCHAR *)arg+4, DLen, &DLen);
280          if (rc)
281          {
282              printf ("DANIS506 ATA GET DSKSP_SMART_GET_ATTRIBUTES failed (%lu)\n", rc);
283              return -1;
284          }
285          break;
286       case ATA_SMART_READ_THRESHOLDS:
287          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_GET_THRESHOLDS,
288       		     (PVOID)&Parms, PLen, &PLen, (UCHAR *)arg+4, DLen, &DLen);
289          if (rc)
290          {
291              printf ("DANIS506 ATA GET DSKSP_SMART_GET_THRESHOLDS failed (%lu)\n", rc);
292              return -1;
293          }
294          break;
295       case ATA_SMART_READ_LOG_SECTOR:
296          buff[4] = buff[1]; // copy select field
297          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_GET_LOG,
298       		     (PVOID)&Parms, PLen, &PLen, (UCHAR *)arg+4, DLen, &DLen);
299          if (rc)
300          {
301              printf ("DANIS506 ATA GET DSKSP_SMART_GET_LOG failed (%lu)\n", rc);
302              return -1;
303          }
304          break;
305       case ATA_SMART_ENABLE:
306          buff[0] = 1; // enable
307          DLen = 1;
308          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_ONOFF,
309 		            (PVOID)&Parms, PLen, &PLen, (PVOID)buff, DLen, &DLen);
310          if (rc) {
311              printf ("DANIS506 ATA GET DSKSP_SMART_ONOFF failed (%lu)\n", rc);
312              return -1;
313          }
314          break;
315       case ATA_SMART_DISABLE:
316          buff[0] = 0; // disable
317          DLen = 1;
318          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_ONOFF,
319 		            (PVOID)&Parms, PLen, &PLen, (PVOID)buff, DLen, &DLen);
320          if (rc) {
321              printf ("DANIS506 ATA GET DSKSP_SMART_ONOFF failed (%lu)\n", rc);
322              return -1;
323          }
324          break;
325 #if 0
326       case ATA_SMART_AUTO_OFFLINE:
327          buff[0] = buff[3];   // select field
328          DLen = 1;
329          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_AUTO_OFFLINE,
330 		            (PVOID)&Parms, PLen, &PLen, (PVOID)buff, DLen, &DLen);
331          if (rc) {
332              printf ("DANIS506 ATA GET DSKSP_SMART_ONOFF failed (%lu)\n", rc);
333              return -1;
334          }
335          break;
336 #endif
337       case ATA_SMART_AUTOSAVE:
338          buff[0] = buff[3];   // select field
339          DLen = 1;
340          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_AUTOSAVE_ONOFF,
341 		            (PVOID)&Parms, PLen, &PLen, (PVOID)buff, DLen, &DLen);
342          if (rc) {
343              printf ("DANIS506 ATA DSKSP_SMART_AUTOSAVE_ONOFF failed (%lu)\n", rc);
344              return -1;
345          }
346          break;
347       case ATA_SMART_IMMEDIATE_OFFLINE:
348          buff[0] = buff[1];   // select field
349          DLen = 1;
350          rc = DosDevIOCtl (hDevice, DSKSP_CAT_SMART, DSKSP_SMART_EXEC_OFFLINE,
351 		            (PVOID)&Parms, PLen, &PLen, (PVOID)buff, DLen, &DLen);
352          if (rc) {
353              printf ("DANIS506 ATA GET DSKSP_SMART_EXEC_OFFLINE failed (%lu)\n", rc);
354              return -1;
355          }
356          break;
357 
358       default:
359          fprintf( stderr, "device %d, arg[0] 0x%x, arg[2] 0x%x\n", device, buff[0], buff[2]);
360          fprintf( stderr, "unknown ioctl\n");
361          return -1;
362          break;
363       }
364       break;
365    //case WIN_PIDENTIFY:
366    //   break;
367    default:
368       fprintf( stderr, "unknown ioctl\n");
369       return -1;
370       break;
371    }
372 
373    // ok
374    return 0;
375 }
376 
377 // Interface to ATA devices.  See os_linux.cpp for the canonical example.
378 // DETAILED DESCRIPTION OF ARGUMENTS
379 //   device: is the integer handle provided by deviceopen()
380 //   command: defines the different operations, see atacmds.h
381 //   select: additional input data IF NEEDED (which log, which type of
382 //           self-test).
383 //   data:   location to write output data, IF NEEDED (1 or 512 bytes).
384 //   Note: not all commands use all arguments.
385 // RETURN VALUES (for all commands BUT command==STATUS_CHECK)
386 //  -1 if the command failed
387 //   0 if the command succeeded,
388 // RETURN VALUES if command==STATUS_CHECK
389 //  -1 if the command failed OR the disk SMART status can't be determined
390 //   0 if the command succeeded and disk SMART status is "OK"
391 //   1 if the command succeeded and disk SMART status is "FAILING"
392 
393 // huge value of buffer size needed because HDIO_DRIVE_CMD assumes
394 // that buff[3] is the data size.  Since the ATA_SMART_AUTOSAVE and
395 // ATA_SMART_AUTO_OFFLINE use values of 0xf1 and 0xf8 we need the space.
396 // Otherwise a 4+512 byte buffer would be enough.
397 #define STRANGE_BUFFER_LENGTH (4+512*0xf8)
398 
ata_command_interface(int device,smart_command_set command,int select,char * data)399 int ata_command_interface(int device, smart_command_set command, int select, char *data){
400   unsigned char buff[STRANGE_BUFFER_LENGTH];
401   // positive: bytes to write to caller.  negative: bytes to READ from
402   // caller. zero: non-data command
403   int copydata=0;
404 
405   const int HDIO_DRIVE_CMD_OFFSET = 4;
406 
407   // See struct hd_drive_cmd_hdr in hdreg.h.  Before calling ioctl()
408   // buff[0]: ATA COMMAND CODE REGISTER
409   // buff[1]: ATA SECTOR NUMBER REGISTER == LBA LOW REGISTER
410   // buff[2]: ATA FEATURES REGISTER
411   // buff[3]: ATA SECTOR COUNT REGISTER
412 
413   // Note that on return:
414   // buff[2] contains the ATA SECTOR COUNT REGISTER
415 
416   // clear out buff.  Large enough for HDIO_DRIVE_CMD (4+512 bytes)
417   memset(buff, 0, STRANGE_BUFFER_LENGTH);
418 
419   //printf( "command, select %d,%d\n", command, select);
420   buff[0]=ATA_SMART_CMD;
421   switch (command){
422   case CHECK_POWER_MODE:
423     buff[0]=ATA_CHECK_POWER_MODE;
424     copydata=1;
425     break;
426   case READ_VALUES:
427     buff[2]=ATA_SMART_READ_VALUES;
428     buff[3]=1;
429     copydata=512;
430     break;
431   case READ_THRESHOLDS:
432     buff[2]=ATA_SMART_READ_THRESHOLDS;
433     buff[1]=buff[3]=1;
434     copydata=512;
435     break;
436   case READ_LOG:
437     buff[2]=ATA_SMART_READ_LOG_SECTOR;
438     buff[1]=select;
439     buff[3]=1;
440     copydata=512;
441     break;
442   case WRITE_LOG:
443     break;
444   case IDENTIFY:
445     buff[0]=ATA_IDENTIFY_DEVICE;
446     buff[3]=1;
447     copydata=512;
448     break;
449   case PIDENTIFY:
450     buff[0]=ATA_IDENTIFY_PACKET_DEVICE;
451     buff[3]=1;
452     copydata=512;
453     break;
454   case ENABLE:
455     buff[2]=ATA_SMART_ENABLE;
456     buff[1]=1;
457     break;
458   case DISABLE:
459     buff[2]=ATA_SMART_DISABLE;
460     buff[1]=1;
461     break;
462   case STATUS:
463   case STATUS_CHECK:
464     // this command only says if SMART is working.  It could be
465     // replaced with STATUS_CHECK below.
466     buff[2]=ATA_SMART_STATUS;
467     buff[4]=0;
468     break;
469   case AUTO_OFFLINE:
470     buff[2]=ATA_SMART_AUTO_OFFLINE;
471     buff[3]=select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
472     break;
473   case AUTOSAVE:
474     buff[2]=ATA_SMART_AUTOSAVE;
475     buff[3]=select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
476     break;
477   case IMMEDIATE_OFFLINE:
478     buff[2]=ATA_SMART_IMMEDIATE_OFFLINE;
479     buff[1]=select;
480     break;
481   //case STATUS_CHECK:
482   //  // This command uses HDIO_DRIVE_TASK and has different syntax than
483   //  // the other commands.
484   //  buff[1]=ATA_SMART_STATUS;
485   //  break;
486   default:
487     pout("Unrecognized command %d in linux_ata_command_interface()\n"
488          "Please contact " PACKAGE_BUGREPORT "\n", command);
489     errno=ENOSYS;
490     return -1;
491   }
492 
493   // We are now calling ioctl wrapper to the driver.
494   // TODO: use PASSTHRU in case of OS2AHCI driver
495   if ((dani_ioctl(device, buff)))
496     return -1;
497 
498   // There are two different types of ioctls().  The HDIO_DRIVE_TASK
499   // one is this:
500   if (command==STATUS_CHECK){
501     // Cyl low and Cyl high unchanged means "Good SMART status"
502     if (buff[4]==0)
503       return 0;
504 
505     // These values mean "Bad SMART status"
506     if (buff[4]==1)
507       return 1;
508 
509     // We haven't gotten output that makes sense; print out some debugging info
510     syserror("Error SMART Status command failed");
511     pout("Please get assistance from " PACKAGE_HOMEPAGE "\n");
512     return -1;
513   }
514 
515   // CHECK POWER MODE command returns information in the Sector Count
516   // register (buff[3]).  Copy to return data buffer.
517   if (command==CHECK_POWER_MODE)
518     buff[HDIO_DRIVE_CMD_OFFSET]=buff[2];
519 
520   // if the command returns data then copy it back
521   if (copydata)
522     memcpy(data, buff+HDIO_DRIVE_CMD_OFFSET, copydata);
523 
524   return 0;
525 }
526 
527 // Interface to SCSI devices. N/A under OS/2
do_scsi_cmnd_io(int,struct scsi_cmnd_io *,int)528 int do_scsi_cmnd_io(int /* fd */, struct scsi_cmnd_io * /* iop */, int /* report */) {
529   pout("SCSI interface is not implemented\n");
530   return -ENOSYS;
531 }
532